【问题标题】:How to Write a Class that will "Know" its own variable name如何编写一个“知道”自己的变量名的类
【发布时间】:2015-12-30 16:17:49
【问题描述】:

假设我有以下类定义:

class my_named_int {
public:
    int value;
    const string name;
    my_named_int(int _value, /*...Does Something Go Here...?*/);
};

我想要的是,如果我编写以下代码:

int main() {
    my_named_int my_name(5);
    std::cout << my_name.name << ":" << my_name.value << std::endl;
}

我希望输出是:

my_name:5

显然,解决此问题的最简单方法是编写如下所示的代码:

class my_named_int {
public:
    int value;
    const string name;
    my_named_int(int _value, const string & _name);
};

int main() {
    my_named_int my_name(5, "my_name");
    std::cout << my_name.name << ":" << my_name.value << std::endl;
}

当然,这增加了我的代码的重复性。有没有办法在 C++ 中做到这一点而无需“双重写入”变量名,如果是,我将如何处理?

【问题讨论】:

  • 你为什么要这样做?
  • 使用宏? C++ 中的编译代码并不真正知道事物的名称[至少不是以代码可以理解的方式 - 调试符号不算数]
  • 不可能(但也许一些 C++ 预处理器技巧可能会有所帮助)
  • 不确定如何做到这一点是 c++ 但我在这里写了一个类似的问题:stackoverflow.com/questions/32148060/get-a-parameters-old-name for c#。如果在 c# 中是可能的,我敢打赌在 c++ 中是可能的
  • 您希望动态分配的my_named_int 使用什么名称?

标签: c++


【解决方案1】:

在 C++ 中变量不知道自己的名字。

你能做的最好的就是使用预处理器:

#define DECLARE_NAMED_INT(value, name) my_named_int name((value), #name);

也就是说这不是惯用的 C++,因此您可能会考虑查看您要解决的真正问题并将 that 作为另一个问题提出。具体来说,我的意思是反射(这似乎是这个问题的意义所在)不是 C++ 特性,因此这些问题通常以其他方式解决。

【讨论】:

  • 这可能更接近我实际想要做的事情。我正在做的很多事情都涉及在编译时检查代码,不一定是在运行时。
  • “不是惯用的 C++”是什么意思?这样做是否违反了重要的设计约定?
  • @Xirema Stroustrup 本人在“C++ 编程语言”中说他希望宏已经被删除。我猜这大致就是“不习惯”的意思。 :-)
  • @cad:另一方面,使用宏是唯一的方法。通常,当存在一些首选方式时,您会说事情不是惯用的——例如,GOTO 在 C++ 中不是惯用的,因为您总是可以使用其他控制结构,这些控制结构在作用域和对象生命周期等方面表现得更好。但在这种情况下,no 方法可以做到不使用宏。
【解决方案2】:

C++ 确实不支持反射作为一种语言特性,但人们可以通过多种方式实现类似反射的属性。

一方面,您可以查看boost::fusion——在融合中,您可以执行以下操作:按顺序迭代结构的所有变量。 Fusion 有一些模板和宏,允许您采用在程序的一部分中声明的任意结构,然后用它进行元编程,就好像它是其成员变量类型的std::tuple。在boost::spirit 文档和示例中有一些有用的实际示例。

但是,Fusion 不跟踪变量的名称,只跟踪它们的类型和顺序。

如果您希望能够在编译时对结构中的变量的类型和名称对列表进行迭代,则可以这样做,但据我所知,它没有库,而且您将不得不推出你自己的东西。 (如果有一个库,我想知道!)

在另一个项目中,我基本上是这样做的(在 C++11 中)如下:

  • 我创建了一个名为serial_struct_field 的类型。这是一个编译时数据结构,它只是一个没有成员的类型,它包含一个名为typetypedef 和一个没有参数的static constexpr 函数,它返回一个名为name 的字符串文字。您还应该有一个与成员变量对应的static constexpr 成员指针。这样做的目的是表示您希望在某些 struct 中拥有关于成员变量的所有编译时元数据。
    (实际上serial_struct_field 是一个概念而不是实际类型,这里不需要继承或任何东西,如您所见。)
  • 我创建了一个名为“serial_struct”的类型特征。串行结构(使用我系统中的某些宏构建)是可以通过此特征提供serial_struct_field 类型的类型列表的结构。有一些模板函数可以检查特征,然后可以根据需要将访问者应用于每个结构成员,因此您可以根据需要以多种不同的方式进行读/写。
    • 例如,您可以制作类似“lua_state_visitor”的东西,可以读取或写入int, double, std::string, std::vector&lt;std::string&gt; 之类的原始内容到lua_State,然后您可以使用lua_state_visitor my_visitor{get_lua_state()}; my_serial_struct.write(my_visitor); 之类的访问者模式或类似的东西。
    • 那么你可以有一个不同的访问者来读写json等。
    • 如果您正确定义它,那么您可以在您的serial_struct 中包含serial_struct,它会正常工作。 :)
  • 为了创建一个串行结构,我提供了三个宏 BEGIN_SERIAL_STRUCTSERIAL_FIELDEND_SERIAL_STRUCT。在代码中它看起来像这样:

    struct foo {
      BEGIN_SERIAL_STRUCT(foo);
      SERIAL_FIELD(std::string, id);
      SERIAL_FIELD(int, my_int);
      SERIAL_FIELD(double, my_double);
      SERIAL_FIELD(std::vector<std::string>, my_vector);
      END_SERIAL_STRUCT;
    
      std::unique_ptr<bar> something_that_cant_really_be_serialized;
      std::shared_ptr<foo_context> more_such_things;
    };
    

    得到的结构本质上是一样的

    struct foo {
      std::string, id;
      int my_int;
      double my_double;
      std::vector<std::string> my_vector;
    
      std::unique_ptr<bar> something_that_cant_really_be_serialized;
      std::shared_ptr<foo_context> more_such_things;
    };
    

    就运行时特性而言,但宏等设置了一些类型和功能,以便您可以进行“通用序列化”。

  • 要实现如图所示的宏本身,您需要能够积累编译时间信息列表。这有点棘手,但在 C++11 中有一些技巧可以做到这一点而不会太复杂。
    如果您愿意将宏更改为如下所示:

      SERIAL_FIELDS(
        (std::string, id),
        (int, my_int),
        (double, my_double),
        (std::vector<std::string>, my_vector));
    

    什么的,那么实现起来就简单多了,基本上可以只使用宏来生成代码,而不必使用辅助模板。
    (但是,这也是一个设计考虑因素,模板通常比宏更健壮,但它们并不总是能很好地协同工作。例如,如果您的序列字段中的类型有多个模板参数(因此逗号)可能会混淆您的宏并给您带来令人麻木的问题,因此对于复杂/可能会变得复杂的应用程序,您最好尽可能使用引擎盖下的模板...)

对于第一个版本,我使用了一种类似于here 描述的技术,但进行了更改,以便它可以全部发生在struct 的定义中。对于第二个版本,如果您只想遍历宏中的列表,您可以使用已知技术,如 here 所述。任何一个版本都只有几百行,而且是独立的。

基本上,如果你真的想要在 C++11 中进行反射,如果你愿意忍受隐藏在你想要使用它的每个结构的一些宏后面的几百行样板代码.. .YMMV

【讨论】:

    【解决方案3】:

    也许这就是你可以使用的:https://github.com/I3ck/cppDbg
    否则使用一个构造函数,它也将一个字符串作为名称,或者自己做一些类似于 cppDbg 的事情

    【讨论】:

      【解决方案4】:

      出现的一个想法是使用 HashMap(或 C++ 中的无序映射)将所有变量绑定到它们所代表的内容。例如,假设您有 my_named_int my_name(5);

      你必须写这个才能把它翻译成

      myMap.insert(std::make_pair<std::string,my_named_int>("my_name",my_name(5));
      

      并用

      检索它
      myMap['my_name]
      

      使用迭代器,您可以循环访问已存储的所有变量。这对于代码构建器可能会派上用场(因为您可以轻松显示所有存储变量的所有值)

      【讨论】:

      • 在实际的最终代码中的某个时候会使用映射,但这并不能解决必须“双写”变量名才能使代码工作的问题。
      • 同时,如果只从map访问,就不用再使用变量名了,所以没有双写:)
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-06-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-04-01
      • 2011-08-26
      • 1970-01-01
      相关资源
      最近更新 更多