这是个有点奇怪的问题。我的目标是理解语言设计决策,并确定在c++中反射的可能性。

为什么c++语言委员会没有在语言中实现反射?在不运行在虚拟机上的语言(如java)中反射是否太困难了? 如果要在c++中实现反射,会遇到什么挑战?

我想反射的用途是众所周知的:编辑器可以更容易地编写,程序代码将更小,可以为单元测试生成模拟等等。但是如果你能评论一下反射的用法就太好了。


当前回答

对于具有反射的语言来说,它是关于编译器愿意在目标代码中保留多少源代码来启用反射,以及有多少分析机制可用来解释反射的信息。除非编译器保留所有源代码,否则反射分析源代码可用事实的能力将受到限制。

c++编译器没有保留任何东西(好吧,忽略RTTI),所以在语言中没有反射。(Java和c#编译器只保留类、方法名和返回类型,所以你只能得到一点点反射数据,但你不能检查表达式或程序结构,这意味着即使在那些“启用反射”的语言中,你能得到的信息也非常少,因此你真的不能做很多分析)。

但是您可以跳出语言,获得完整的反射功能。C语言中关于反射的另一个堆栈溢出讨论的答案讨论了这个问题。

其他回答

在c++中有几个关于反射的问题。

It's a lot of work to add, and the C++ committee is fairly conservative, and don't spend time on radical new features unless they're sure it'll pay off. (A suggestion for adding a module system similar to .NET assemblies has been made, and while I think there's general consensus that it'd be nice to have, it's not their top priority at the moment, and has been pushed back until well after C++0x. The motivation for this feature is to get rid of the #include system, but it would also enable at least some metadata). You don't pay for what you don't use. That's one of the must basic design philosophies underlying C++. Why should my code carry around metadata if I may never need it? Moreover, the addition of metadata may inhibit the compiler from optimizing. Why should I pay that cost in my code if I may never need that metadata? Which leads us to another big point: C++ makes very few guarantees about the compiled code. The compiler is allowed to do pretty much anything it likes, as long as the resulting functionality is what is expected. For example, your classes aren't required to actually be there. The compiler can optimize them away, inline everything they do, and it frequently does just that, because even simple template code tends to create quite a few template instantiations. The C++ standard library relies on this aggressive optimization. Functors are only performant if the overhead of instantiating and destructing the object can be optimized away. operator[] on a vector is only comparable to raw array indexing in performance because the entire operator can be inlined and thus removed entirely from the compiled code. C# and Java make a lot of guarantees about the output of the compiler. If I define a class in C#, then that class will exist in the resulting assembly. Even if I never use it. Even if all calls to its member functions could be inlined. The class has to be there, so that reflection can find it. Part of this is alleviated by C# compiling to bytecode, which means that the JIT compiler can remove class definitions and inline functions if it likes, even if the initial C# compiler can't. In C++, you only have one compiler, and it has to output efficient code. If you were allowed to inspect the metadata of a C++ executable, you'd expect to see every class it defined, which means that the compiler would have to preserve all the defined classes, even if they're not necessary. And then there are templates. Templates in C++ are nothing like generics in other languages. Every template instantiation creates a new type. std::vector<int> is a completely separate class from std::vector<float>. That adds up to a lot of different types in a entire program. What should our reflection see? The template std::vector? But how can it, since that's a source-code construct, which has no meaning at runtime? It'd have to see the separate classes std::vector<int> and std::vector<float>. And std::vector<int>::iterator and std::vector<float>::iterator, same for const_iterator and so on. And once you step into template metaprogramming, you quickly end up instantiating hundreds of templates, all of which get inlined and removed again by the compiler. They have no meaning, except as part of a compile-time metaprogram. Should all these hundreds of classes be visible to reflection? They'd have to, because otherwise our reflection would be useless, if it doesn't even guarantee that the classes I defined will actually be there. And a side problem is that the template class doesn't exist until it is instantiated. Imagine a program which uses std::vector<int>. Should our reflection system be able to see std::vector<int>::iterator? On one hand, you'd certainly expect so. It's an important class, and it's defined in terms of std::vector<int>, which does exist in the metadata. On the other hand, if the program never actually uses this iterator class template, its type will never have been instantiated, and so the compiler won't have generated the class in the first place. And it's too late to create it at runtime, since it requires access to the source code. And finally, reflection isn't quite as vital in C++ as it is in C#. The reason is again, template metaprogramming. It can't solve everything, but for many cases where you'd otherwise resort to reflection, it's possible to write a metaprogram which does the same thing at compile-time. boost::type_traits is a simple example. You want to know about type T? Check its type_traits. In C#, you'd have to fish around after its type using reflection. Reflection would still be useful for some things (the main use I can see, which metaprogramming can't easily replace, is for autogenerated serialization code), but it would carry some significant costs for C++, and it's just not necessary as often as it is in other languages.

编辑: 在回应评论时:

cdleary: Yes, debug symbols do something similar, in that they store metadata about the types used in the executable. But they also suffer from the problems I described. If you've ever tried debugging a release build, you'll know what I mean. There are large logical gaps where you created a class in the source code, which has gotten inlined away in the final code. If you were to use reflection for anything useful, you'd need it to be more reliable and consistent. As it is, types would be vanishing and disappearing almost every time you compile. You change a tiny little detail, and the compiler decides to change which types get inlined and which ones don't, as a response. How do you extract anything useful from that, when you're not even guaranteed that the most relevant types will be represented in your metadata? The type you were looking for may have been there in the last build, but now it's gone. And tomorrow, someone will check in a small innocent change to a small innocent function, which makes the type just big enough that it won't get completely inlined, so it'll be back again. That's still useful for debug symbols, but not much more than that. I'd hate trying to generate serialization code for a class under those terms.

Evan Teran: Of course these issues could be resolved. But that falls back to my point #1. It'd take a lot of work, and the C++ committee has plenty of things they feel is more important. Is the benefit of getting some limited reflection (and it would be limited) in C++ really big enough to justify focusing on that at the expense of other features? Is there really a huge benefit in adding features the core language which can already (mostly) be done through libraries and preprocessors like QT's? Perhaps, but the need is a lot less urgent than if such libraries didn't exist. For your specific suggestions though, I believe disallowing it on templates would make it completely useless. You'd be unable to use reflection on the standard library, for example. What kind of reflection wouldn't let you see a std::vector? Templates are a huge part of C++. A feature that doesn't work on templates is basically useless.

But you're right, some form of reflection could be implemented. But it'd be a major change in the language. As it is now, types are exclusively a compile-time construct. They exist for the benefit of the compiler, and nothing else. Once the code has been compiled, there are no classes. If you stretch yourself, you could argue that functions still exist, but really, all there is is a bunch of jump assembler instructions, and a lot of stack push/pop's. There's not much to go on, when adding such metadata.

但就像我说的,有一个修改编译模型的建议,添加自包含的模块,为选择的类型存储元数据,允许其他模块引用它们,而不必使用#includes。这是一个很好的开始,说实话,我很惊讶标准委员会没有因为这个改变太大而把这个提议否决掉。所以也许在5-10年后?:)

Reflection requires some metadata about types to be stored somewhere that can be queried. Since C++ compiles to native machine code and undergoes heavy changes due to optimization, high level view of the application is pretty much lost in the process of compilation, consequently, it won't be possible to query them at run time. Java and .NET use a very high level representation in the binary code for virtual machines making this level of reflection possible. In some C++ implementations, however, there is something called Run Time Type Information (RTTI) which can be considered a stripped down version of reflection.

如果c++可以:

变量名、变量类型和const修饰符的类成员数据 函数参数迭代器(只有位置而不是名称) 函数名、返回类型和const修饰符的类成员数据 父类列表(与定义的顺序相同) 模板成员和父类的数据;扩展的模板(意味着实际的类型将可用于反射API,而不是“如何到达那里的模板信息”)

这足以在无类型数据处理的关键处创建非常容易使用的库,而无类型数据处理在当今的web和数据库应用程序中非常普遍 (所有的orm,消息传递机制,xml/json解析器,数据序列化等)。

例如,Q_PROPERTY宏(Qt框架的一部分)支持的基本信息 http://qt.nokia.com/doc/4.5/properties.html扩展到涵盖类方法和e) -将对c++和一般的软件社区非常有益。

当然,我所指的反射不会涵盖语义或更复杂的问题(如注释、源代码行号、数据流分析等)——但我也不认为这些是语言标准的一部分。

c++是一种不需要反射的语言,因为c++是一种可以用来编写具有反射的语言的语言。

在过去的10年里,人们一直在尝试向c++中添加反射。最新的提案是针对c++23的,可能会,也可能不会。

与大多数语言中的反射不同,c++反射的计划是编译时反射。所以在编译时,你可以反射结构成员、函数和方法参数和属性、枚举值和名称等。

然后,您可以进行有限的具体化,注入关于反射的信息以生成其他类型和代码。

虽然这有点奇怪,但这意味着不使用反射的程序不会为它支付运行时成本。它也非常强大。

最简单的例子是,您可以使用它来实现运行时反射。

struct Member {
  std::string_view name;
  std::any_ref value;
};

struct Reflectable {
  virtual std::span<Member> GetMembers() const = 0;
  virtual std::span<Member> GetMembers() = 0;
};

template<class D>
struct ImplReflectable:Reflectable {
  std::span<Member> GetMembers() const final;
  std::span<Member> GetMembers() final;
};
template<class D>
std::span<Member> ImplReflectable<D>::GetMembers() const {
  // compile time reflection code on D here
}
template<class D>
std::span<Member> ImplReflectable<D>::GetMembers() {
  // compile time reflection code on D here
}

你把上面的代码写了一次,突然你就可以对任何你想要反射的类型,你可以这样做:

struct Point : ImplReflectable<Point> {
  int x, y;
};

和一个反射系统连接到点。

实现此运行时反射的库可以像您喜欢的那样复杂和强大。每种类型都必须做一些工作(如上所述)才能选择加入,但对于UI库(例如)这样做并不是一个严重的问题。没有选择的类型延续了c++的假设:“如果你不使用它,就不要为它付费”。

但这仅仅是个开始。一个提议,元类,允许:

interface Reflectable {
  std::span<Member> GetMembers() const;
  std::span<Member> GetMembers();
};

您可以使用元类或接受类型并返回类型的函数。这允许您定义类的元类,如“interface”,用语言编写。现在,接口有点像玩具,但是你可以编写QObject或Reflectable或PolymorphicValueType或NetworkProtocol元类来修改你的类定义的含义。

这可能会也可能不会出现在c++23中。它会继续变得更好,但也会继续被推回去。对于大多数主要的c++编译器,您可以尝试多种编译时反射实现。语法是不断变化的,因为有基于符号运算符的反射库,基于reflexpr的运算符反射库,其中一些反射数据是类型,另一些是constexpr对象和consteval函数。