这是一个有点奇怪的问题。我的目标是了解语言设计决策,并确定在C ++中反射的可能性。
C++语言委员会为什么没有朝着在语言中实现反射的方向发展?在不运行虚拟机(如java)的语言中,反射是否过于困难?
如果要为C ++实现反射,将面临哪些挑战?
我想反射的用途已经很清楚了:可以更容易地编写编辑器,程序代码将更小,可以为单元测试生成模拟对象等。但如果您能评论一下反射的用途,那就太好了。
这是一个有点奇怪的问题。我的目标是了解语言设计决策,并确定在C ++中反射的可能性。
C++语言委员会为什么没有朝着在语言中实现反射的方向发展?在不运行虚拟机(如java)的语言中,反射是否过于困难?
如果要为C ++实现反射,将面临哪些挑战?
我想反射的用途已经很清楚了:可以更容易地编写编辑器,程序代码将更小,可以为单元测试生成模拟对象等。但如果您能评论一下反射的用途,那就太好了。
C++中反射存在几个问题。
添加反射的工作量很大,C++委员会比较保守,除非他们确信新特性一定有回报,否则不会花太多时间来推广。曾经提出过一个类似.NET程序集的模块系统,虽然我认为这是一个不错的想法,但目前该特性并非他们的首要任务,已经被推迟到C++0x之后。引入该特性的动机是为了摆脱#include
系统,但也可以至少启用一些元数据。
C++设计的基本哲学之一是,不用的东西不需要付费。为什么我可能永远不需要的元数据要存在我的代码中?此外,添加元数据可能会阻碍编译器的优化。如果我可能永远不需要那些元数据,为什么要在我的代码中付出这样的代价呢?
这也引出了另一个重要的问题:C++对编译后的代码几乎没有作出任何保证。只要功能符合预期,编译器就可以做任何它喜欢的事情。例如,你的类并不一定必须真正“存在”。编译器可以将它们优化掉,在线扩展其所有操作,并且经常确实这样做,因为即使简单的模板代码也往往会生成相当多的模板实例化。C++标准库依赖于这种积极的优化。如果对象实例化和销毁的开销能够被优化掉,函数对象才能表现出良好的性能。与原始数组索引相比,向量上的operator[]
仅在性能方面有可比性,因为整个运算符都可以内联处理并从编译后的代码中完全删除。C#和Java在编译器输出的内容方面都有很多保证。如果我在C#中定义了一个类,那么即使我从未使用它,该类也将存在于生成的程序集中,并且所有对其成员函数的调用都可以被内联。类必须存在,以便反射可以找到它。部分原因是C#编译成字节码,这意味着JIT编译器可以在初始C#编译器无法进行优化时删除类定义并内联函数。在C++中,只有一个编译器,它必须输出高效的代码。如果允许您检查C++可执行文件的元数据,则期望看到它定义的每个类,这意味着编译器必须保留所有定义的类,即使它们不是必需的。
然后是模板。C++中的模板与其他语言中的泛型完全不同。每个模板实例化都会创建一个新类型。 std::vector<int>
是与std::vector<float>
完全独立的类。在整个程序中,这会增加大量不同的类型。我们的反射应该看到什么?模板std::vector
?但是它怎么可能呢?因为那是一个源代码结构,在运行时没有意义。它必须看到单独的类std::vector<int>
和std::vector<float>
,以及std::vector<int>::iterator
和std::vector<float>::iterator
,同样适用于const_iterator
等等。一旦您进入模板元编程,您很快就会实例化数百个模板,所有这些模板都会被编译器内联并删除。它们没有意义,除非作为编译时元程序的一部分。应该将这些数百个类全部显示给反射吗?他们必须这样做,否则我们的反射将是无用的,因为它甚至不能保证我定义的类实际上会“存在”。而一个副问题是,模板类在实例化之前不存在。想象一
但是你说得对,可以实现某种形式的反射。但这将是语言中的重大变化。目前,类型是仅在编译时存在的构造。它们存在于编译器的好处,除此之外没有别的作用。一旦代码被编译,就没有类存在。如果你自己思考,你可能会认为函数仍然存在,但事实上,只有一堆跳转汇编指令和大量的栈推入/弹出。当添加这样的元数据时,可供参考的信息很少。
但正如我所说,有一个更改编译模型的提案,添加自包含的模块,存储选定类型的元数据,允许其他模块引用它们而不必处理#include
。这是一个良好的开端,老实说,我很惊讶标准委员会没有因为变化太大而放弃该提案。所以也许五到十年后呢? :)
export
和vector<bool>
之后。 - David Thornleytypeinfo
的name()
函数必须返回程序员键入的名称,而不是未定义的内容。并且还要为枚举器提供字符串化程序。这对于序列化/反序列化、帮助制造工厂等方面实际上非常重要。 - v.oddou反射需要存储一些类型元数据以便可以查询。由于C ++编译为本机机器代码,并且由于优化而发生了大量变化,因此在编译过程中丢失了应用程序的高级视图,因此无法在运行时查询它们。Java和.NET在虚拟机的二进制代码中使用非常高级别的表示,因此可以进行这种级别的反射。但是,在某些C++实现中,有一些称为运行时类型信息(RTTI)的内容,可以被认为是反射的简化版本。
所有编程语言不应尝试将每种其他编程语言的功能都整合进去。
C++本质上是一个非常复杂的宏汇编器。 它不是(传统意义上的)高级语言,如C#,Java,Objective-C,Smalltalk等。
拥有不同的工具用于不同的工作是好的。 如果我们只有锤子,所有东西看起来都像钉子一样。 有脚本语言对于某些工作很有用,而反射式OO语言(Java,Obj-C,C#)对于另一类工作很有用,而超高效的近乎裸机语言对于另一类工作也很有用(C ++,C,汇编语言)。
C ++在将汇编技术扩展到难以想象的复杂性管理和抽象层面方面做得非常出色,使得编程更大,更复杂的任务变得更加可能。 但是它不一定是最适合从严格的高级角度来解决问题的语言(Lisp,Smalltalk,Java,C#)。 如果您需要具有这些功能的语言来最好地实现解决问题的方案,则感谢那些为我们所有人创建这些语言的人!
但是C ++是为那些因某种原因需要代码与基础机器操作之间有强烈关联的人设计的。 无论是效率,还是编写设备驱动程序,或者与低级别OS服务交互等,C ++都更适合这些任务。
C#,Java,Objective-C都需要一个更大,更丰富的运行时系统来支持它们的执行。 那个运行时系统必须预先安装到相关系统中以支持您的软件的操作。 并且那个层次必须为各种目标系统进行定制,由某种其他语言编写,以使其在该平台上工作。 而那个中间层 - 主机OS和您的代码之间的自适应层 - 运行时几乎总是以效率为第一位的语言(如C或C ++),其中可以很好地理解和操纵软件与硬件之间的精确交互,以达到最大利益。
我喜欢Smalltalk、Objective-C以及具有反射、元数据、垃圾回收等丰富运行时系统。利用这些功能可以编写出惊人的代码!但那只是在堆栈上的一个更高层次,必须建立在更低的层次之上,而它们自己最终必须坐落在操作系统和硬件之上。我们始终需要一种最适合构建该层的语言:C++/C/汇编。
补充说明:C++11/14不断扩展C++支持更高级别的抽象和系统。线程、同步、精确内存模型、更精确的抽象机器定义使C++开发人员能够实现一些高级语言曾经独有的高级抽象,同时继续提供接近于底层的性能和优秀的可预测性(即最小化运行时子系统)。也许将来会有选择性地启用反射功能,供那些需要的人使用,或者一个库将提供这样的运行时服务(现在可能已经有了,或者在boost中已经开始了一个库)。
mmap
,从这个意义上说,它为语言提供了一个分配器。有时它也支持sbrk
,以及堆栈页面承诺、堆栈保护页面访问信号、分段错误信号、sigbus、管道创建、线程创建、filber创建、上下文切换支持、文件系统抽象、管道等等... 对我来说,这是一个巨大的运行时。 - v.oddou对于支持反射的编程语言来说,编译器愿意在目标代码中保留多少源代码以启用反射,以及可以解释反射信息的分析机制有多少决定了反射的能力。如果编译器没有保留所有源代码,那么反射将受到限制,无法分析源代码的所有可用信息。
C++编译器不会保留任何内容(忽略RTTI),因此您无法在语言中获得反射。(Java和C#编译器仅保留类、方法名称和返回类型,因此您可以获得一些反射数据,但无法检查表达式或程序结构,这意味着即使在这些“启用反射”的语言中,您可以获取的信息也非常有限,因此您实际上无法进行太多分析)。
但是,您可以跨越语言界限获得完整的反射能力。关于在C语言中实现反射的问题,另一个Stack Overflow讨论的答案 提供了相关信息。
它不是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;
};
并且反射系统附加到Point
。
实现此运行时反射的库可以像您喜欢的那样复杂和强大。每种类型都必须做一些工作(如上所述)才能选择加入,但对于 UI 库(例如),这样做并不是一个严重的问题。不选择加入的类型继续使用 C++ 的假设:“如果您不使用它,则不需要为其付费”。
但这只是个开始。提议中的元类允许:
interface Reflectable {
std::span<Member> GetMembers() const;
std::span<Member> GetMembers();
};
有很多情况下需要在C++中使用反射,而这些情况无法通过编译时构造(如模板元编程)充分解决。
N3340 提出了丰富指针作为引入C++反射的一种方式。其中,它解决了只有在使用时才需要付费的问题,同时还有其他功能。