Answers:
在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
等等。一旦进入模板元编程,您很快就会实例化成百上千个模板,所有这些模板都会被内联并由编译器再次删除。除了作为编译时元程序的一部分外,它们没有任何意义。所有这数百个类都应该可以反射吗?他们必须这样做,因为如果不保证我定义的类实际上在那里,我们的反思将是无用的。另一个问题是模板类在实例化之前是不存在的。想象一个使用的程序std::vector<int>
。我们的反射系统应该能够看到std::vector<int>::iterator
吗?一方面,您当然希望如此。这是一类重要的,并且它在定义的std::vector<int>
,它确实存在于元数据中。另一方面,如果程序从不实际使用此迭代器类模板,则其类型将永远不会被实例化,因此编译器将不会首先生成该类。并且在运行时创建它为时已晚,因为它需要访问源代码。
boost::type_traits
是一个简单的例子。您想了解类型
T
吗?检查它type_traits
。在C#中,您必须使用反射在其类型之后钓鱼。反射在某些方面仍然有用(我看到的主要用途是元编程不能轻易替换,用于自动生成的序列化代码),但是它会为C ++带来一些可观的成本,并且没有必要经常使用用其他语言。编辑: 回应评论:
cdleary:是的,调试符号的作用类似,因为它们存储有关可执行文件中使用的类型的元数据。但是他们也遭受我所描述的问题的困扰。如果您曾经尝试调试发行版,您将了解我的意思。在源代码中创建类的地方存在很大的逻辑鸿沟,该类已内联到最终代码中。如果要将反射用于有用的任何内容,则需要使其更加可靠和一致。实际上,几乎在每次编译时,类型都会消失和消失。您只需更改一些小细节,然后编译器就决定更改哪些类型可以内联,哪些类型不可以内联,作为响应。当您这样做时,如何从中提取有用的信息 甚至不保证最相关的类型将在您的元数据中表示?您要查找的类型可能在上一版中已经存在,但是现在已经消失了。明天,有人将签入一个小的无辜更改为一个小的无辜函数,这会使类型足够大而不会被完全内联,因此它将再次返回。这对于调试符号仍然有用,但仅此而已。我讨厌尝试根据这些条款为一个类生成序列化代码。但仅此而已。我讨厌尝试根据这些条款为一个类生成序列化代码。但仅此而已。我讨厌尝试根据这些条款为一个类生成序列化代码。
埃文·特兰:当然,这些问题可以得到解决。但这又回到了我的第一点。这需要大量的工作,并且C ++委员会还有很多他们认为更重要的事情。在C ++中获得一些有限的反射(并且会受到限制)的好处真的足够大,足以证明以牺牲其他功能为代价专注于此吗?在核心功能中添加可以(大多数)已经通过QT的库和预处理器完成的功能真的有巨大的好处吗?也许吧,但是与不存在这样的库相比,这种需求的紧迫性要低得多。不过,对于您的具体建议,我相信禁止在模板上使用它会使其完全无用。例如,您将无法在标准库上使用反射。什么样的反思不会std::vector
?模板是C ++ 的重要组成部分。在模板上不起作用的功能基本上是无用的。
但是您是对的,可以实现某种形式的反思。但这将是语言的重大变化。到目前为止,类型仅是编译时构造。它们的存在是为了编译器的利益,而没有别的。一旦代码被编译,也都没有课。如果您费力地讲,您可能会说函数仍然存在,但实际上,所有这些都是一堆跳转汇编器指令以及很多堆栈推入/弹出指令。添加此类元数据时,没有太多的事情要做。
但是就像我说的那样,有人建议更改编译模型,添加自包含模块,存储选择类型的元数据,允许其他模块引用它们而不必弄乱#include
s。这是一个很好的开始,说实话,我很惊讶标准委员会没有因为改变太大而把提案扔掉了。那么也许在5-10年后?:)
export
和之后vector<bool>
。
typeinfo
的name()
函数必须返回在程序员,这也不是未定义输入的名称。同时也给我们一个枚举器。这实际上对于序列化/反序列化,帮助制造工厂等至关重要
反射要求将有关类型的一些元数据存储在可以查询的位置。由于C ++会编译为本机代码并由于优化而进行重大更改,因此在编译过程中会丢失很多高级应用程序视图,因此,无法在运行时对其进行查询。Java和.NET在虚拟机的二进制代码中使用非常高级的表示形式,从而使这种级别的反映成为可能。但是,在某些C ++实现中,有一种称为运行时类型信息(RTTI)的东西,可以将其视为反射的简化版本。
所有语言都不应尝试合并其他每种语言的所有功能。
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的开始?)。
如果您真的想了解有关C ++的设计决策,请阅读Ellis和Stroustrup撰写的《The Annotated C ++ Reference Manual》。它不是最新的标准,但会通过原始标准,并解释事物的工作方式以及通常的工作方式。
具有语言的语言的反射是,编译器愿意在目标代码中保留多少源代码以启用反射,以及有多少可用的分析机制来解释该反射信息。除非编译器保留所有源代码,否则反射将无法分析有关源代码的可用事实。
C ++编译器不保留任何东西左右(当然,忽视RTTI),所以你没有得到反映在语言。(Java和C#编译器仅保留类,方法名称和返回类型,因此您可以获得少量反射数据,但无法检查表达式或程序结构,这意味着即使使用那些“启用了反射功能”的语言,您可以获得的信息很少,因此您真的无法做太多分析)。
但是,你可以一步之外的语言,并得到充分反映能力。有关C中反射的另一个堆栈溢出讨论的答案对此进行了讨论。
C ++没有反射的原因是,这将要求编译器向目标文件添加符号信息,例如类类型具有的成员,有关成员的信息,有关函数以及所有内容的信息。从本质上讲,这将使包含文件无用,因为然后将从那些目标文件(然后是模块)中读取声明提供的信息。在C ++中,类型定义可以通过包含各自的头而在程序中多次出现(假设所有这些定义都相同),因此必须决定将有关该类型的信息放在何处,就像命名一个一样。并发症在这里。由C ++编译器进行的积极优化(可以优化数十个类模板实例化)是另一个优势。有可能,但是由于C ++与C兼容,
根据Alistair Cockburn的说法,在反射环境中无法保证子类型化。
反射与潜在打字系统更相关。在C ++中,您知道所拥有的类型,并且知道可以使用它做什么。
如果C ++可以:
const
修饰符的类成员数据const
修饰符的类成员数据这足以在当今Web和数据库应用程序(所有规范,消息传递机制,xml / json解析器,数据序列化等)中如此普遍的无类型数据处理的关键点上创建非常易于使用的库。
例如,Q_PROPERTY
宏(Qt Framework的一部分)
支持的基本信息http://qt.nokia.com/doc/4.5/properties.html扩展以覆盖类方法和e)-对于C ++和一般的软件社区。
当然,我指的是反射不会涵盖语义或更复杂的问题(例如注释源代码行号,数据流分析等),但我认为这些都不必成为语言标准的一部分。
在C ++中进行反思,我认为,如果将C ++用作数据库访问,Web会话处理/ http和GUI开发的语言,则至关重要。缺少反射会阻止ORM(例如Hibernate或LINQ),实例化类的XML和JSON解析器,数据序列化和许多其他种类(最初必须使用无类型数据来创建类的实例)。
可以在构建过程中使用软件开发人员可用的编译时间开关来消除这种“您为所用内容付费”的问题。
我是一位固件开发人员,无需反射即可从串行端口读取数据-那么请不要使用该开关。但是,作为一个想要继续使用C ++的数据库开发人员,我经常使用令人讨厌,难以维护的代码在数据成员和数据库结构之间映射数据。
Boost序列化和其他机制都不能真正解决反射问题-它必须由编译器完成-完成后,C ++将再次在学校变得困难,并用于处理数据处理的软件中
对我来说,这个问题是#1(而天真线程原语是问题#2)。