为什么C ++没有反射?
这是一个有点奇怪的问题。 我的目标是理解语言设计决策,并确定在C ++中反思的可能性。
为什么C ++语言委员会没有去实施语言反思? 在没有运行在虚拟机上的语言(如java)中,反射太困难了?
如果有人为C ++实现反思,那么挑战是什么?
我想反射的用途是众所周知的:编辑器可以更容易编写,程序代码会更小,可以为单元测试生成模拟等等。 但是,如果您也可以评论反射的用途,那将是非常好的。
在C ++中反射有几个问题。
需要添加很多工作,并且C ++委员会相当保守,除非他们确信它会得到回报,否则不要花时间在全新的功能上。 (有人建议增加一个类似于.NET程序集的模块系统,虽然我认为普遍认为它会很高兴,但这不是他们当前的首要任务,并且一直推迟到很晚C ++ 0x这个功能的动机是摆脱#include
系统,但它也会启用至少一些元数据)。
你不支付你不使用的东西。 这是C ++基础设计哲学之一。 为什么我的代码需要携带元数据,如果我不需要它? 而且,元数据的添加可能会阻碍编译器的优化。 如果我可能永远不需要这些元数据,为什么我应该在代码中支付这些费用?
这导致我们得到另一个重要的观点:C ++对编译的代码做出很少的保证。 只要得到的功能是预期的,编译器就可以做任何它喜欢的事情。 例如,你的课程并不需要真正在那里。 编译器可以对它们进行优化,内联他们所做的一切,而且它经常这样做,因为即使是简单的模板代码也会创建很多模板实例。 C ++标准库依赖于这种积极的优化。 如果可以优化实例化和破坏对象的开销,那么函数只是表现性的。 矢量上的operator[]
与性能中的原始数组索引相比只是可比较的,因为整个运算符可以内联并因此完全从编译代码中移除。 C#和Java对编译器的输出做出了很多保证。 如果我用C#定义一个类,那么这个类将存在于生成的程序集中。 即使我从不使用它。 即使所有对其成员函数的调用都可以内联。 班必须在那里,以便反思可以找到它。 部分原因可以通过C#编译为字节码来缓解,这意味着即使最初的C#编译器不能,JIT编译器也可以删除类定义和内联函数(如果它喜欢的话)。 在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
等是一样的。 一旦你进入模板元编程,你很快就会实例化数以百计的模板,所有这些模板都会被编译器内联和删除。 除了作为编译时元程序的一部分,它们没有任何意义。 这些数百个类是否应该反映出来? 他们不得不这样做,否则我们的反思将是无用的,如果它甚至不能保证我定义的类实际上会在那里。 另一个问题是模板类在实例化之前不存在。 想象一下使用std::vector<int>
。 我们的反射系统应该能够看到std::vector<int>::iterator
? 一方面,你一定会这样想。 这是一个重要的类,它是根据std::vector<int>
,它存在于元数据中。 另一方面,如果程序从未真正使用过这个迭代器类模板,它的类型将永远不会被实例化,所以编译器首先不会生成类。 在运行时创建它为时已晚,因为它需要访问源代码。
boost::type_traits
是一个简单的例子。 你想知道T
型吗? 检查其type_traits
。 在C#中,你必须在使用反射的类型之后进行修改。 反射对于某些事情仍然有用(我可以看到的主要用途,元编程无法轻易取代,主要用于自动生成的序列化代码),但是它会为C ++带来一些重要的成本,并且它不是必须的是用其他语言。 编辑:回复评论:
cdleary:是的,调试符号做类似的事情,因为它们存储关于可执行文件中使用的类型的元数据。 但他们也受到我所描述的问题的困扰。 如果你曾经尝试过调试发布版本,你会明白我的意思。 在源代码中创建了一个类时,存在很大的逻辑差距,在最终代码中已经将其插入。 如果你使用反射来获得有用的东西,你需要它更加可靠和一致。 事实上,类型几乎每次编译都会消失。 你改变了一个小小的细节,编译器决定改变哪些类型是内联的,哪些不内联,作为回应。 当你甚至不能保证最相关的类型将在你的元数据中表示时,你如何从中提取有用的东西? 您正在寻找的类型可能在最后一次构建中存在,但现在它已经消失。 明天,有人会检查一个小的无辜的变化,以一个小的无辜的功能,这使得这种类型足够大,它不会完全内联,所以它会再次回来。 这对于调试符号仍然很有用,但不仅限于此。 我讨厌试图在这些条款下为一个类生成序列化代码。
Evan Teran:当然这些问题可以解决。 但是这又回到了我的观点#1。 这需要很多工作,C ++委员会有很多他们认为更重要的事情。 在C ++中获得一些有限反思(并且它会受到限制)的好处真的足够大,足以证明以牺牲其他功能为代价的重点? 核心语言已经(大部分)可以通过库和预处理器(如QT)来完成,是否真的有很大的好处? 也许,但是这种需求比没有这种库的情况要少得多。 不过,对于您的具体建议,我相信在模板上禁用它会使其完全无用。 例如,您将无法在标准库上使用反射。 什么样的反射不会让你看到std::vector
? 模板是C ++的一个重要组成部分。 在模板上不起作用的功能基本上是无用的。
但是你是对的,可以实施某种形式的反思。 但这将是语言的一个重大变化。 就目前而言,类型只是编译时构造。 它们是为编译器的利益而存在的,没有其他的。 代码编译完成后,就没有类。 如果你自己伸展自己,你可能会认为函数仍然存在,但是实际上,所有这些都有一堆跳转汇编指令,以及大量的堆栈push / pop。 在添加这样的元数据时,没有什么可继续。
但正如我所说,有一个改变编译模型的建议,增加了自包含的模块,为选择类型存储元数据,允许其他模块引用它们而不必与#include
。 这是一个好的开始,说实话,我很惊讶,标准委员会并不仅仅因为太大的改变而放弃提案。 所以也许在5 - 10年内? :)
反射需要一些关于类型的元数据存储在可以查询的地方。 由于C ++编译为本地机器代码,并且由于优化而发生重大变化,因此在编译过程中应用程序的高级视图几乎失去了,因此在运行时将无法查询它们。 Java和.NET在虚拟机的二进制代码中使用非常高级的表示,使得这种反射级别成为可能。 然而,在一些C ++实现中,有一些称为运行时类型信息(RTTI)的东西,它可以被认为是反射的简化版本。
所有的语言都不应该试图将每一种其他语言的每个特征都包含进来
C ++本质上是一个非常非常复杂的宏汇编器。 它不是传统意义上的C#,Java,Objective-C,Smalltalk等高级语言。
对不同的工作有不同的工具是很好的。 如果我们只有锤子,所有的东西都会看起来像钉子等。脚本语言对于某些工作很有用,反射OO语言(Java,Obj-C,C#)对于另一类作业很有用,而超级有效的裸机接近机器语言对于另一类作业(C ++,C,Assembler)很有用。
C ++在将Assembler技术扩展到令人难以置信的复杂性管理水平方面做得非常出色,抽象化使得编程更大,更复杂的任务对人类来说更为可能。 但它不一定是最适合那些从严格的高层次角度来看待他们的问题的语言(Lisp,Smalltalk,Java,C#)。 如果您需要具备这些功能的语言以最好地解决您的问题,那么请感谢那些为我们所有人创建了这些语言的人!
但是对于那些无论出于何种原因,C ++都需要在代码和底层机器的操作之间有很强关联的人。 无论其效率,编程设备驱动程序,还是与较低级别的OS服务交互(或其他),C ++都更适合这些任务。
C#,Java,Objective-C都需要更大更丰富的运行时系统来支持它们的执行。 该运行时间必须交付给相关系统 - 预安装以支持您的软件的运行。 并且该层必须针对各种目标系统进行维护,通过某种其他语言进行定制以使其在该平台上工作。 而中间层 - 主机操作系统和代码之间的自适应层 - 运行时,几乎总是用C或C ++语言编写,效率为#1,可以预见地理解软件和硬件之间的确切交互可以很好地实现理解和操纵以获得最大收益。
我喜欢Smalltalk,Objective-C,并拥有丰富的运行时系统,包括反射,元数据,垃圾收集等。可以编写出惊人的代码来充分利用这些设施! 但是,这只是堆栈中的一个更高层,它必须位于较低层,它们本身最终必须位于操作系统和硬件之上。 我们将始终需要一种最适合构建该层的语言:C ++ / C / Assembler。
附录:C ++ 11/14正在继续扩展C ++支持更高级别抽象和系统的能力。 线程,同步,精确的内存模型,更精确的抽象机器定义使C ++开发人员能够实现许多高级别抽象,这些高级别唯一语言曾被用于专有领域,同时继续提供近距离,金属性能和出色的可预测性(即最小的运行时子系统)。 也许反思设施将在C ++的未来版本中有选择性地启用,对于那些想要它的人 - 或者图书馆可能会提供这样的运行时服务(现在可能有一个,或者是一个提升的开始?)。
链接地址: http://www.djcxy.com/p/8355.html上一篇: Why does C++ not have reflection?
下一篇: How to read the value of a private field from a different class in Java?