为什么C ++没有反射?

问题描述 投票:321回答:14

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

  1. 为什么C ++语言委员会不会在语言中实现反思?对于不在虚拟机上运行的语言(如java),反射是否太难?
  2. 如果要实现C ++的反射,那么挑战是什么?

我想反射的使用是众所周知的:编辑器可以更容易编写,程序代码更小,可以为单元测试生成模拟等等。但是,如果你也可以对反射的使用发表评论,那就太棒了。

c++ reflection
14个回答
606
投票

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>::iteratorstd::vector<float>::iteratorconst_iterator相同,等等。一旦你进入模板元编程,你很快就会最终实例化数百个模板,所有模板都会被编译器内联并再次删除。它们没有任何意义,除非作为编译时元程序的一部分。所有这几百个课程都应该可以反映出来吗?他们必须这样做,因为否则我们的反思就会毫无用处,如果它甚至不能保证我定义的类实际上会在那里。另一个问题是模板类在实例化之前不存在。想象一下使用std::vector<int>的程序。我们的反射系统应该能够看到std::vector<int>::iterator吗?一方面,你当然希望如此。它是一个重要的类,它是根据std::vector<int>定义的,它确实存在于元数据中。另一方面,如果程序从未实际使用此迭代器类模板,则其类型将永远不会被实例化,因此编译器将不会首先生成该类。而且在运行时创建它为时已晚,因为它需要访问源代码。
  • 最后,反射在C ++中并不像在C#中那样重要。原因是模板元编程。它无法解决所有问题,但是在许多情况下,除非您采用反射,否则可以编写一个在编译时执行相同操作的元程序。 boost::type_traits就是一个简单的例子。你想知道关于T的类型吗?检查它的type_traits。在C#中,你必须使用反射在它的类型之后钓鱼。反射对于某些事情仍然有用(我可以看到的主要用途,哪个元编程不能轻易替换,用于自动生成的序列化代码),但它会为C ++带来一些重要的成本,并且它不需要像它一样频繁。是用其他语言。

编辑:回应评论:

cdleary:是的,调试符号做类似的事情,因为它们存储有关可执行文件中使用的类型的元数据。但他们也遇到了我所描述的问题。如果你曾尝试调试发布版本,你就会明白我的意思。在源代码中创建了一个类,存在很大的逻辑空白,它已经在最终代码中被内联了。如果您使用反射来做任何有用的事情,那么您需要它更可靠和一致。事实上,几乎每次编译时类型都会消失和消失。您更改了一个小细节,编译器决定更改哪些类型内联,哪些类型不作为响应。当您甚至不能保证在元数据中表示最相关的类型时,如何从中提取任何有用的内容?您正在寻找的类型可能已经在最后一次构建中存在,但现在它已经消失了。明天,有人会将一个无辜的小变化检查到一个小小的无辜功能,这使得该类型足够大,不会完全内联,所以它会再次回来。这对于调试符号仍然有用,但不仅仅是这些。我讨厌在这些条款下为类生成序列化代码。

Evan Teran:当然这些问题可以解决。但这又回到了我的观点#1。它需要做很多工作,C ++委员会有很多他们认为更重要的事情。在C ++中得到一些有限的反思(并且它会受到限制)的好处是否真的足以证明以牺牲其他功能为代价来关注它?添加功能核心语言是否真的有很大的好处,这些功能已经(大部分)通过库和预处理器(如QT)完成了?也许,但是,如果不存在这样的图书馆,那么需求就不那么紧迫了。但是,对于您的具体建议,我认为在模板上禁止它会使它完全无用。例如,您无法在标准库上使用反射。什么样的反射不会让你看到std::vector?模板是C ++的重要组成部分。对模板不起作用的功能基本上没用。

但你是对的,可以实施某种形式的反思。但这是语言的一个重大变化。就像现在一样,类型只是一个编译时构造。它们的存在是为了编译器的利益,而不是其他任何东西。编译代码后,就没有类。如果你伸展自己,你可能会认为函数仍然存在,但实际上,所有的都是一堆跳转汇编指令,以及大量的堆栈推送/弹出。添加此类元数据时,没有太多事情可做。

但就像我说的那样,有一个改进编译模型的建议,添加自包含的模块,存储选择类型的元数据,允许其他模块引用它们而不必混淆#includes。这是一个良好的开端,说实话,我很惊讶标准委员会并没有因为太大的改变而抛出提案。也许在5到10年内? :)


2
投票

反射可以是可选的,就像预处理器指令一样。就像是

#pragma enable reflection

这样我们可以充分利用这两个世界,没有反射就可以创建这个编译指示库(没有讨论任何开销),那么无论他们想要速度还是易用性都会影响个体开发人员。


2
投票

如果C ++可以:

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

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

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

当然,我所指的反映不会涵盖语义或更复杂的问题(如评论源代码行号,数据流分析等) - 但我认为这些都不需要成为语言标准的一部分。



0
投票

如果要将C ++用作数据库访问,Web会话处理/ http和GUI开发的语言,我相信C ++中的反思至关重要。缺乏反射会阻止ORM(如Hibernate或LINQ),XML和JSON解析器实现类,数据序列化和许多其他方面(最初无类型数据必须用于创建类的实例)。

在构建过程中可供软件开发人员使用的编译时开关可用于消除“您为所使用的内容付费”的问题。

我的固件开发人员不需要反射来从串口读取数据 - 然后很好地不使用开关。但作为一个想继续使用C ++的数据库开发人员,我不断地分阶段使用一个可怕的,难以维护的代码,在数据成员和数据库结构之间映射数据。

Boost序列化和其他机制都没有真正解决反射 - 它必须由编译器完成 - 一旦完成,C ++将再次在学校中进行,并用于处理数据处理的软件中

对我来说这个问题#1(和本机线程原语是问题#2)。


0
投票

这基本上是因为它是一个“可选的额外”。许多人选择C ++而不是像Java和C#这样的语言,这样他们就可以更好地控制编译器输出,例如:一个更小,更快的程序。

如果你选择添加反射,那就有various solutions available


38
投票

反射需要一些有关类型的元数据存储在可以查询的地方。由于C ++编译为本机机器代码并且由于优化而经历了大量更改,因此在编译过程中应用程序的高级视图几乎丢失,因此,无法在运行时查询它们。 Java和.NET在虚拟机的二进制代码中使用非常高级别的表示,使得这种反射水平成为可能。然而,在一些C ++实现中,存在称为运行时类型信息(RTTI)的东西,其可被视为反射的精简版本。


15
投票

所有语言都不应该尝试包含所有其他语言的所有功能。

C ++本质上是一个非常非常复杂的宏汇编程序。它不是(传统意义上的)高级语言,如C#,Java,Objective-C,Smalltalk等。

为不同的工作提供不同的工具是很好的。如果我们只有锤子,所有的东西都会看起来像指甲等。有了脚本语言对某些工作很有用,反射的OO语言(Java,Obj-C,C#)对另一类工作很有用,而且超级 - 高效的裸机接近机器语言对于另一类作业(C ++,C,汇编程序)非常有用。

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 ++版本中有选择地启用反射工具 - 或者也许库会提供这样的运行时服务(现在可能有一个,或者在boost中有一个开头?)。


11
投票

如果您真的想了解围绕C ++的设计决策,请查找Ellis和Stroustrup的The Annotated C++ Reference Manual副本。它不是最新的标准,但它符合原始标准,并解释了事情是如何工作的,并且经常是如何实现的。


9
投票

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

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

但是你可以走出语言并获得全面的反射功能。关于reflection in C的另一个堆栈溢出讨论的答案讨论了这一点。


8
投票

Reflection can be and has been implemented in c++ before.

它不是本机c ++特性,因为它具有很高的成本(内存和速度),默认情况下不应该由语言设置 - 语言是“默认的最大性能”。

因为你不应该为你不需要的东西买单,而且你自己说它在编辑器中比在其他应用程序中需要的更多,那么它应该只在你需要的地方实现,而不是“强制”到所有的代码(您不需要在编辑器或其他类似应用程序中反映您将使用的所有数据。


6
投票

C ++没有反射的原因是,这需要编译器将符号信息添加到目标文件中,例如类类型的成员,有关成员的信息,有关函数和所有内容的信息。这实际上会使包含文件无用,因为声明传送的信息将从那些目标文件(模块)中读取。在C ++中,类型定义可以在程序中多次出现,包括相应的头文件(前提是所有这些定义都相同),因此必须决定在何处放置有关该类型的信息,就像命名一个复杂在这里。由C ++编译器完成的积极优化可以优化数十个类模板实例化,这是另一个优点。这是可能的,但由于C ++与C兼容,这将成为一个尴尬的组合。


3
投票

在C ++中使用反射的案例很多,使用模板元编程等编译时结构无法充分解决。

N3340提出了丰富的指针,作为在C ++中引入反射的一种方式。除了你使用它之外,它解决了不支付功能的问题。


2
投票

根据Alistair Cockburn,subtyping can't be guaranteed in a reflective environment

反射与潜在的打字系统更相关。在C ++中,你知道你有什么类型,你知道你可以用它做什么。

© www.soinside.com 2019 - 2024. All rights reserved.