这是个有点奇怪的问题。我的目标是理解语言设计决策,并确定在c++中反射的可能性。
为什么c++语言委员会没有在语言中实现反射?在不运行在虚拟机上的语言(如java)中反射是否太困难了? 如果要在c++中实现反射,会遇到什么挑战?
我想反射的用途是众所周知的:编辑器可以更容易地编写,程序代码将更小,可以为单元测试生成模拟等等。但是如果你能评论一下反射的用法就太好了。
这是个有点奇怪的问题。我的目标是理解语言设计决策,并确定在c++中反射的可能性。
为什么c++语言委员会没有在语言中实现反射?在不运行在虚拟机上的语言(如java)中反射是否太困难了? 如果要在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函数。
其他回答
在过去的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函数。
这基本上是因为它是一个“可选的额外项目”。许多人选择c++而不是Java和c#等语言,这样他们可以更好地控制编译器的输出,例如,一个更小和/或更快的程序。
如果您选择添加反射,有各种可用的解决方案。
The reason C++ doesn't have reflection is that this would require the compilers to add symbol information to the object files, like what members a class type has, information about the members, about the functions and everything. This essentially would render include files useless, as information shipped by declarations would then be read from those object files (modules then). In C++, a type definition can occur multiple times in a program by including the respective headers (provided that all those definitions are the same), so it would have to be decided where to put the information about that type, just as to name one complication here. The aggressive optimization done by a C++ compiler, which can optimize out dozens of class template instantiations, is another strong point. It's possible, but as C++ is compatible to C, this would become an awkward combination.
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.
根据Alistair Cockburn的说法,在反射环境中不能保证子类型。
反射与潜在类型系统更相关。在c++中,你知道你得到了什么类型,你知道你可以用它做什么。