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


为什么C ++语言委员会不致力于在语言中实现反思?用不能在虚拟机上运行的语言(例如Java)进行反射是否太困难了?
如果要为C ++实现反射,将面临哪些挑战?

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

#1 楼

C ++中的反射存在多个问题。确保它会还清。 (已经提出了添加类似于.NET程序集的模块系统的建议,尽管我认为已经有了一个很好的共识,但这并不是当前的重中之重,并且一直推迟到很久以后C ++ 0x。此功能的用意是摆脱#include系统,但它也至少会启用一些元数据。
您不用为不使用的东西付钱
。那是C ++的基本基本设计哲学之一。
如果我可能永远不需要我的代码,为什么我的代码应该随身携带
元数据?
此外,元数据的添加
可能会阻止编译器优化。如果我可能永远不需要元数据,为什么要在我的代码中支付这笔费用?关于编译后的代码。只要编译器
做任何它想做的事情,只要
所期望的功能就是
。例如,您的
类实际上并不需要
。编译器可以对其进行优化,内联
做所有事情,并且
经常这样做,因为
即使是简单的模板代码也往往会
创建很多模板
实例。 C ++标准库
依靠这种积极的优化。如果可以最优化实例化和销毁
对象的开销,则函子才是有效的。性能
,因为整个操作符可以被内联,从而可以从编译后的代码中完全删除
。 C#和Java
为编译器的
输出提供了很多保证。如果我定义
C#中的一个类,则该类将
存在于生成的程序集中。
即使我从不使用它。即使所有
对其成员函数的调用都可以内联。该类必须存在,以便反射可以找到它。通过C#
编译为字节码,可以部分缓解这种情况,这意味着
JIT编译器可以根据需要删除
类定义和内联
函数,即使
初始C#编译器不能。在C ++中,您只有一个编译器,并且它必须输出有效的代码。如果您
被允许检查C ++可执行文件的元数据
,那么您希望
查看其定义的每个类,这
意味着编译器将具有
保留所有已定义的类,即使它们不是必需的。
然后有模板。
C ++中的模板与其他语言中的泛型不一样。每个
模板实例化都会创建一个
新类型。 operator[]是与
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进行定义。另一方面,如果程序从未真正使用过此迭代器类模板,则其类型将永远不会被实例化,因此编译器将不会首先生成该类。而且在运行时创建它为时已晚,因为它需要访问源代码。最后,反射在C ++中并不像在C#中那么重要。
原因再次是模板
元编程。它不能解决所有问题,但是在许多情况下,如果您不得不采用
反射,则可以编写一个执行相同功能的
元程序
std::vector<int>是一个简单的示例。您想了解类型
boost::type_traits吗?检查其T。在C#中,您必须使用反射在其
类型之后钓鱼。反射
仍然对某些事情有用(我可以看到的主要用途是
元编程不能轻易替换
,用于自动生成的
序列化代码) ,但它会为C ++带来一些高昂的成本,并且没有其他语言那么频繁。

编辑:
评论:

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

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

但是您是对的,可以实现某种形式的反射。但这将是语言的重大变化。到目前为止,类型仅是编译时构造。它们的存在是为了编译器的利益,仅此而已。一旦代码被编译,就没有类了。如果您费力地讲,您可能会争辩说函数仍然存在,但是实际上,所有这些都是一堆跳转汇编器指令以及很多堆栈推入/弹出指令。添加此类元数据时,没有太多的事情要做。模块引用它们而不必弄乱std::vector。这是一个良好的开端,说实话,我很惊讶标准委员会没有因为变更太大而把提案扔掉了。那么也许在5-10年后? :)

评论


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

– cdleary
08年12月11日在21:03

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

– David Thornley
09年11月6日在17:04

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

– ak牛-亚当·内夫罗蒙特
2012-12-12 22:41

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

–超级猫
13年4月4日在20:55

可以肯定的是,可以通过COMITEE轻松地进行某些改进,就是说白底黑字,typeinfo的name()函数必须返回程序员输入的名称,而不是未定义的名称。同时也给我们一个枚举器。这实际上对于序列化/反序列化,帮助制造工厂等至关重要。

–v.oddou
13年4月18日在2:13



#2 楼

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

评论


RTTI在C ++标准中。

–丹尼尔(Daniel Earwicker)
08-12-12在7:27

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

–mmx
08年12月14日在9:44

而且大多数支持RTTI的实现也支持通过编译器选项将其关闭。

–迈克尔·科恩(Michael Kohne)
09年11月6日在20:12

#3 楼

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

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

评论


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

–理查德·罗斯三世(Richard J. Ross III)
13年5月7日在23:01

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

–莫达猜
13年5月8日在21:32



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

–理查德·罗斯三世(Richard J. Ross III)
13年5月9日在12:43

“ C运行时”只是一个动态库,其中包含C标准库的代码。与“ C ++运行时”相同。它与Objective-C之类的运行时系统完全不同。另外...虽然我认为您可以在技术上使用C语言中的Objective-C运行时,但这仍然只是一个使用Objective-C运行时的C程序-您无法在C中编译实际的Objective-C程序。

– Celticminstrel
15年7月24日在15:23

具有内存模型+原子的C ++ 11使其更像便携式汇编程序。这些不是高级的东西,它们是C ++以前缺乏可移植支持的低级东西。但是,如果您做错了任何事情,C ++中的UB数量将使其与Java等基于VM的语言非常不同,并且与任何特定的汇编语言也不相同。例如在C ++源代码中,signed-overflow完全是UB,即使编译为x86,编译器也可以基于该事实进行优化,但是在几乎所有平台上的asm中,它都将环绕。现代C ++与可移植的汇编语言相距甚远。

– Peter Cordes
18年8月14日在10:20

#4 楼

如果您真的想了解有关C ++的设计决策,请参阅Ellis和Stroustrup撰写的《 The Annotated C ++ Reference Manual》。它不是最新的标准,但会沿用原始标准,并解释事物的工作方式和频率,以及它们是如何获得的。

评论


还由Stroustrup设计和开发C ++

–詹姆斯·霍普金(James Hopkin)
08-12-11在16:26

#5 楼

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

C ++编译器没有保留任何内容(很好,忽略了RTTI),因此您不会对这种语言有所了解。 (Java和C#编译器仅保留类,方法名称和返回类型,因此您可以获得少量反射数据,但无法检查表达式或程序结构,这意味着即使使用那些“启用了反射功能”的语言,您可以获取的信息非常稀疏,因此您真的无法进行太多分析。)

但是您可以跳出语言并获得全面的反思能力。关于C中反射的另一个堆栈溢出讨论的答案对此进行了讨论。

#6 楼

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

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

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

评论


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

– gbjbaanb
08年12月11日在13:06

您是对的,即使代码暴露问题我也没有:)

–克莱姆
08年12月11日在13:38

#7 楼

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

评论


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

–罗伯·肯尼迪
08年12月11日在13:39

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

–杰夫
08-12-11在13:47

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

– Johannes Schaub-小人
09年8月31日在12:25

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

–约瑟夫·加文
09年11月28日在6:51

#8 楼

在C ++中使用反射的很多情况无法通过模板元编程等编译时结构来充分解决。

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

#9 楼

根据Alistair Cockburn的说法,不能在反射环境中确保子类型化。

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

评论


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

–超级猫
13 Mar 4 '13 at 20:42

#10 楼

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

#pragma enable reflection

这样一来,我们就可以兼得两全其美,没有这个杂物库,就不会产生反思(没有讨论的任何开销),那么它无论他们想要速度还是易于使用,都将取决于单个开发人员。

#11 楼

如果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 ++。

–伊拉克·巴克斯特
2011年6月19日下午5:51

#12 楼

我刚刚发现了一些有关C ++反射的良好链接:

C ++标准的工作论文:C ++中反射的方面

使用模板进行反射的简单示例

#13 楼

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

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

#14 楼

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

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

我不需要固件开发人员反射以从串行端口读取数据-那么请不要使用该开关。但是,作为一个想要继续使用C ++的数据库开发人员,我经常使用令人讨厌,难以维护的代码来逐步在数据成员和数据库结构之间映射数据。反思-必须由编译器完成-完成后,C ++将再次在学校变得困难,并用于处理数据处理的软件中。

对我来说,这个问题#1(和天真线程基元是问题2)。

评论


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

–杰夫
09-09-11 15:36

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

–土狼21
2011年5月30日20:15



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

– cHao
2012年4月17日14:56



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

– MrFox
2012年7月6日19:46

#15 楼

C ++是一种不需要反射的语言,因为C ++是一种您可以用来编写其中具有反射性的语言的语言。