为什么C ++没有反射?


337

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

  1. 为什么C ++语言委员会不致力于在语言中实现反射?使用不在虚拟机上运行的语言(如Java)进行反射是否太困难了?

  2. 如果要实现对C ++的反思,将面临哪些挑战?

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

Answers:


631

在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>::iterator,同为const_iterator等等。一旦进入模板元编程,您很快就会实例化成百上千个模板,所有这些模板都会被内联并由编译器再次删除。除了作为编译时元程序的一部分外,它们没有任何意义。所有这数百个类都应该可以反射吗?他们必须这样做,因为如果不保证我定义的类实际上在那里,我们的反思将是无用的。另一个问题是模板类在实例化之前是不存在的。想象一个使用的程序std::vector<int>。我们的反射系统应该能够看到std::vector<int>::iterator吗?一方面,您当然希望如此。这是一类重要的,并且它在定义的std::vector<int>,它确实存在于元数据中。另一方面,如果程序从不实际使用此迭代器类模板,则其类型将永远不会被实例化,因此编译器将不会首先生成该类。并且在运行时创建它为时已晚,因为它需要访问源代码。

  • 最后,反射在C ++中的重要性不如C#。原因再次是模板元编程。它不能解决所有问题,但是在许多情况下,如果您不得不依靠反射,可以编写一个在编译时执行相同操作的元程序。 boost::type_traits是一个简单的例子。您想了解类型 T吗?检查它type_traits。在C#中,您必须使用反射在其类型之后钓鱼。反射在某些方面仍然有用(我看到的主要用途是元编程不能轻易替换,用于自动生成的序列化代码),但是它会为C ++带来一些可观的成本,并且没有必要经常使用用其他语言。

编辑: 回应评论:

cdleary:是的,调试符号的作用类似,因为它们存储有关可执行文件中使用的类型的元数据。但是他们也遭受我所描述的问题的困扰。如果您曾经尝试调试发行版,您将了解我的意思。在源代码中创建类的地方存在很大的逻辑鸿沟,该类已内联到最终代码中。如果要将反射用于有用的任何内容,则需要使其更加可靠和一致。实际上,几乎在每次编译时,类型都会消失和消失。您只需更改一些小细节,然后编译器就决定更改哪些类型可以内联,哪些类型不可以内联,作为响应。当您这样做时,如何从中提取有用的信息 甚至不保证最相关的类型将在您的元数据中表示?您要查找的类型可能在上一版中已经存在,但是现在已经消失了。明天,有人将签入一个小的无辜更改为一个小的无辜函数,这会使类型足够大而不会被完全内联,因此它将再次返回。这对于调试符号仍然有用,但仅此而已。我讨厌尝试根据这些条款为一个类生成序列化代码。但仅此而已。我讨厌尝试根据这些条款为一个类生成序列化代码。但仅此而已。我讨厌尝试根据这些条款为一个类生成序列化代码。

埃文·特兰:当然,这些问题可以得到解决。但这又回到了我的第一点。这需要大量的工作,并且C ++委员会还有很多他们认为更重要的事情。在C ++中获得一些有限的反射(并且会受到限制)的好处真的足够大,足以证明以牺牲其他功能为代价专注于此吗?在核心功能中添加可以(大多数)已经通过QT的库和预处理器完成的功能真的有巨大的好处吗?也许吧,但是与不存在这样的库相比,这种需求的紧迫性要低得多。不过,对于您的具体建议,我相信禁止在模板上使用它会使其完全无用。例如,您将无法在标准库上使用反射。什么样的反思不会std::vector?模板是C ++ 的重要组成部分。在模板上不起作用的功能基本上是无用的。

但是您是对的,可以实现某种形式的反思。但这将是语言的重大变化。到目前为止,类型仅是编译时构造。它们的存在是为了编译器的利益,而没有别的。一旦代码被编译,也没有课。如果您费力地讲,您可能会说函数仍然存在,但实际上,所有这些都是一堆跳转汇编器指令以及很多堆栈推入/弹出指令。添加此类元数据时,没有太多的事情要做。

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


2
这些问题中的大多数不是已经必须通过调试符号来解决吗?并不是说它会表现出色(因为您提到了内联和优化),但是您可以通过执行任何调试符号来考虑反射的可能性
cdleary

2
关于您的第一点的另一件事:据我所知,没有人尝试过向C ++实现添加反射。没有很好的经验。委员会可能不愿带头,特别是在export和之后vector<bool>
David Thornley,2009年

18
我同意C ++不应具有运行时反射。但是编译时反射很少有上述问题,如果有人选择,则可用于某人在特定类上构建运行时反射。是否可以通过模板访问类的第n个方法和第n个父类的类型,名称和功能?并在编译时得到这样的数量?可以使基于CRTP的自动反射成为可能,而没有人为他们不使用的东西付费。
Yakk-Adam Nevraumont 2012年

15
在很多方面,您的第三点是最重要的:C ++旨在适合在内存成本高昂的平台上编写独立代码;如果消除一些未使用的代码将使程序适合于花费$ 2.00的微控制器,而不是花费$ 2.50的微控制器,并且如果代码以1,000,000单位运行,则消除该代码可节省$ 500,000。如果没有反思,静态分析通常可以识别90%以上的不可达代码。如果允许反射,则必须假定通过反射可以到达的所有内容都是可以到达的,即使其中90%不能到达。
2013年

2
肯定是有东西,可以很容易地通过comitee得到改善,这是最后说黑白色是typeinfoname()函数必须返回在程序员,这也不是未定义输入的名称。同时也给我们一个枚举器。这实际上对于序列化/反序列化,帮助制造工厂等至关重要
v.oddou

38

反射要求将有关类型的一些元数据存储在可以查询的位置。由于C ++会编译为本机代码并由于优化而进行重大更改,因此在编译过程中会丢失很多高级应用程序视图,因此,无法在运行时对其进行查询。Java和.NET在虚拟机的二进制代码中使用非常高级的表示形式,从而使这种级别的反映成为可能。但是,在某些C ++实现中,有一种称为运行时类型信息(RTTI)的东西,可以将其视为反射的简化版本。


15
RTTI在C ++标准中。
Daniel Earwicker

1
但是,并非所有的C ++实现都是标准的。我见过不支持RTTI的实现。
Mehrdad Afshari

3
而且,大多数支持RTTI的实现也支持通过编译器选项将其关闭。
迈克尔·科恩

21

所有语言都不应尝试合并其他每种语言的所有功能。

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 ++语言编写的,可以很好地理解软件和硬件之间的精确交互理解并操纵以获得最大收益。

我喜欢Smalltalk,Objective-C,并拥有一个具有反射,元数据,垃圾收集等功能的丰富运行时系统。可以编写惊人的代码以利用这些功能!但这只是堆栈上的较高层,必须位于较低层上,而它们本身最终必须位于操作系统和硬件上。我们将始终需要最适合构建该层的语言:C ++ / C / Assembler。

附录:C ++ 11/14正在继续扩展C ++的能力,以支持更高级别的抽象和系统。线程,同步,精确的内存模型,更精确的抽象机定义使C ++开发人员能够实现许多高级抽象,而这些高级唯一语言中的某些曾经用于它们的专有域,同时继续提供接近金属性能和出色的可预测性(即最小的运行时子系统)。也许在将来的C ++版本中,有需要的人可以有选择地启用反射功能,或者一个库将提供这样的运行时服务(也许现在有一个,或者是boost的开始?)。


在Objective-C的情况下,关于必须要用另一种语言编译的语言的运行时间的观点是不正确的,因为它的运行时间是用C编写的(Objective-C是其超集)。
理查德·罗斯三世

这是没有区别的区别。最终,Objective-C使用的运行时子系统实际上不是用Objective-C而是用C编写的,它有什么区别?
Mordachai

3
对不起; 但是只要您正确链接它,就可以用C编译一个Objective-C程序,实际上我是在这里完成的:stackoverflow.com/a/10290255/427309。您上面的全部陈述都是错误的。运行时可通过C完全访问,它是使它成为强大的动态语言的原因之一。
理查德·罗斯三世

1
“ C运行时”只是一个动态库,其中包含C标准库的代码。与“ C ++运行时”相同。它与Objective-C之类的运行时系统完全不同。此外...当我想你可以在技术上使用C中的Objective-C运行,这仍然只是一个C程序,它使用Objective-C运行-你不能在C编译实际的Objective-C程序
celticminstrel

2
Ç具有++ 11的存储器模型+原子学使它类似于便携式汇编程序。这些都不是高层次的东西,他们是层次的东西,C ++原先缺乏的便携式支持。但是,如果您做错了任何事情,C ++中的UB数量将使其与Java等基于VM的语言非常不同,并且与任何特定的汇编语言也不相同。例如,签名溢出在C ++源代码中完全是UB,并且编译器即使基于 x86 进行编译也可以基于该事实进行优化,但是在几乎所有平台上的asm中,它都将环绕。现代C ++与可移植的汇编语言相距甚远。
彼得·科德斯


9

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

C ++编译器不保留任何东西左右(当然,忽视RTTI),所以你没有得到反映语言。(Java和C#编译器仅保留类,方法名称和返回类型,因此您可以获得少量反射数据,但无法检查表达式或程序结构,这意味着即使使用那些“启用了反射功能”的语言,您可以获得的信息很少,因此您真的无法做太多分析)。

但是,你可以一步之外的语言,并得到充分反映能力。有关C中反射的另一个堆栈溢出讨论的答案对此进行了讨论。


7

反射可以并且已经在c ++中实现过。

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

由于您不应该为不需要的东西付费,并且正如您自己说的那样,与其他应用程序相比,它在编辑器中需要的更多,因此应仅在需要的地方实现它,而不是“强制”使用所有代码(您不需要对将要在编辑器或其他类似应用程序中使用的所有数据进行反思)。


3
并且您不会发布符号,因为它会使您的客户/竞争对手查看您的代码...这通常被认为是一件坏事。
gbjbaanb

您说得对,即使我没有提到代码
公开

6

C ++没有反射的原因是,这将要求编译器向目标文件添加符号信息,例如类类型具有的成员,有关成员的信息,有关函数以及所有内容的信息。从本质上讲,这将使包含文件无用,因为然后将从那些目标文件(然后是模块)中读取声明提供的信息。在C ++中,类型定义可以通过包含各自的头而在程序中多次出现(假设所有这些定义都相同),因此必须决定将有关该类型的信息放在何处,就像命名一个一样。并发症在这里。由C ++编译器进行的积极优化(可以优化数十个类模板实例化)是另一个优势。有可能,但是由于C ++与C兼容,


1
我不明白编译器的积极优化是多么强。你能详细说明吗?如果链接器可以删除重复的内联函数定义,那么重复的反射信息有什么问题?对于调试器,符号信息是否仍未添加到目标文件中吗?
罗伯·肯尼迪

1
问题在于您的反射信息可能无效。如果编译器消除了80%的类定义,那么反射元数据会说什么?在C#和Java中,该语言保证如果您定义一个类,则该类将保持定义状态。C ++让编译器对其进行优化。
jalf

1
@Rob,优化是另一点,与多类复杂性无关。请参阅@jalf的评论(和他的回答)以了解我的意思。
Johannes Schaub-litb

4
如果我实例化反射<T>,那么不要丢弃任何T的信息。这似乎不是一个无法解决的问题。
约瑟夫加文

3

在C ++中有大量使用反射的情况,使用模板元编程等编译时结构无法充分解决。

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


2

根据Alistair Cockburn的说法,在反射环境中无法保证子类型化

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


更一般而言,如果不引入未定义行为就可以检查不存在的功能,则可以将该功能添加到类的更高版本中,这将改变预先存在的程序的明确定义的行为,并且可能因此,无法保证添加该功能不会“破坏”某些功能。
2013年

2

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

#pragma enable reflection

这样一来,我们就可以兼得两全其美,而无需创建该杂注库就不会产生反思(没有讨论的任何开销),那么每个开发人员都需要他们自己提高速度还是易于使用。


2

如果C ++可以:

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

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

例如,Q_PROPERTY宏(Qt Framework的一部分) 支持的基本信息http://qt.nokia.com/doc/4.5/properties.html扩展以覆盖类方法和e)-对于C ++和一般的软件社区。

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


@Vlad:是的,如果添加了支持反射语言的功能,您就会得到该语言的反射。这只有在语言委员会颁布法令的情况下才有可能发生,而且我认为他们在2011年还没有颁布,我怀疑在2020年之前还会出台另一种C ++标准。所以,好主意。同时,如果您想取得进步,则可能必须退出C ++。
伊拉·巴克斯特


0

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

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

我是一位固件开发人员,无需反射即可从串行端口读取数据-那么请不要使用该开关。但是,作为一个想要继续使用C ++的数据库开发人员,我经常使用令人讨厌,难以维护的代码在数据成员和数据库结构之间映射数据。

Boost序列化和其他机制都不能真正解决反射问题-它必须由编译器完成-完成后,C ++将再次在学校变得困难,并用于处理数据处理的软件中

对我来说,这个问题是#1(而天真线程原语是问题#2)。


4
谁说过C ++ 被用作数据库访问语言,Web会话hnadling或GUI开发工具?有很多更好的语言可用于此类工作。并且编译时切换不会解决问题。通常,启用或禁用反射的决定不会基于每个文件。如果可以在单个类型上启用它,则它可以工作。如果程序员在定义类型时可以指定属性或类似属性,则是否应生成该属性的反射元数据。但是全球性的转变?您可能会折损90%的语言,以使其简化10%。
jalf

然后,如果我想要一个跨平台的程序并可以访问gui,我应该使用什么?僵化的Java摇摆?Windows只有C#?但是,应该说实话,事实是,有很多程序是用可执行代码编译的,并提供gui接口和对数据库的访问,因此它们必须使用一些数据库和gui支持... t使用QT。(应该将其命名为MT(怪物工具包))
Coyote11年

1
@ Coyote21:多年来,C#不仅限于Windows。(尽管我不是Mono的粉丝,但它对大多数内容都足够好。)而且Swing并不是Java的唯一GUI工具包。实话实说,如果您想要跨平台,则任何一个都是更好的选择。如果您做一些不重要的事情,C ++几乎总是会在这里或那里拥有特定于平台的部分。
cHao 2012年

没有理由需要对ORM进行反思。您可以使用模板来实现所有这些目标。有一些框架为C ++提供ORM。
MrFox 2012年

0

基本上是因为它是“可选的附加项”。许多人选择C ++而不是Java和C#之类的语言,以便他们对编译器输出有更多控制权,例如更小和/或更快速的程序。

如果选择添加反射,则可以使用多种解决方案

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.