如何编写将"Know"自己的变量名的类

How to Write a Class that will "Know" its own variable name

本文关键字:变量名 自己的 Know 何编写      更新时间:2023-10-16

所以我们假设我有以下类定义:

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++中,变量不知道自己的名称。

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

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

也就是说,这不是惯用的C++,所以你可以考虑看看你试图解决的真正问题,然后把这个作为另一个问题来问。具体来说,我的意思是反射(这似乎就是这个问题的意义所在)不是C++的一个特性,因此这些问题通常以其他方式解决。

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<std::string>之类的原始内容到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));
    

    或者其他什么,那么它的实现要简单得多,而且基本上可以只使用宏来生成代码,而不必使用辅助模板
    (但是,这也是一个设计考虑因素,模板通常比宏更健壮,而且它们并不总是能很好地配合在一起。例如,如果序列字段中的类型有多个模板参数(以及逗号),这些参数可能会混淆宏,给你带来令人麻木的问题,因此,对于一个复杂/可能会变得复杂的应用程序,您最好尽可能多地在后台使用模板…)

在第一个版本中,我使用了一种类似于这里描述的技术,但进行了更改,使其可以在struct的定义中进行。对于第二个版本,如果您只想在宏中迭代一个列表,可以使用这里描述的已知技术。任何一个版本都只有几百行,并且是自包含的。

基本上,如果你真的想在C++11中使用反射,如果你愿意为你想在其中使用的每个结构在一些宏后面隐藏几百行锅炉板…YMMV

也许这就是你可以使用的:https://github.com/I3ck/cppDbg
否则,请使用一个也以字符串为名称的构造函数,或者执行类似于cppDbg中的操作

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

你必须写这篇文章才能将其翻译成

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

并用检索

myMap['my_name]

使用迭代器,您可以遍历存储的所有变量。这对于代码生成器来说可能很有用(因为您可以很容易地显示所有存储变量的所有值)