为啥 C++ 继承机制不透明?
Posted
技术标签:
【中文标题】为啥 C++ 继承机制不透明?【英文标题】:Why are C++ inheritance mechanisms opaque?为什么 C++ 继承机制不透明? 【发布时间】:2009-09-12 18:25:51 【问题描述】:例如,为什么没有语言支持来检查 vtable?为什么我不能用新的成员函数替换成员函数?我有一种直觉,有办法充分利用这些功能。
还有其他语言可以让我做这些事情吗?
【问题讨论】:
我很确定,弄乱 vtable 将不会使代码更具可读性/安全/可靠。 如果有对此的语言支持,我不会认为它是“乱来的”。这将是一种清晰易懂且广泛使用的编程实践。 是的,但是没有,所以... 如果您对此的最佳理由是“直觉”,那可能就是您的答案。 C++ 委员会通常不喜欢添加一些东西,除非有明确、具体的用例。 【参考方案1】:因为它是编译器的一个实现细节。该实现可能会改变,任何依赖它的代码充其量都是脆弱的。
【讨论】:
【参考方案2】:C++ 是一种你永远不会为你不使用的东西“付费”的语言。这种运行时支持与这种理念背道而驰。
有很多语言(在更动态的一端)支持这一点。
【讨论】:
能否请您说出一些支持此功能的语言。我脑海中的声音说 javascript 和 Python……但不确定。 我不知道具体是 JavaScript 还是 Python 支持,但是 Ruby 肯定是支持的。 要获得真正强大的对象系统,请查看“CLOS”和“MOP”。 Javascript、Python、Ruby、Lisp——你几乎可以选择任何动态语言,它会拥有它。【参考方案3】:因为它不必被实现为VTable
,尽管通常是这样。总之,C++中没有VTable
这样的东西!
【讨论】:
【参考方案4】:主要原因是保留 vtable 作为实现细节允许任何具体的实现在它认为合适的时候对其进行优化;这意味着它可以例如如果可以证明给定方法(或所有方法)没有虚拟调用,则修剪甚至完全消除 vtable。或者它可以用 if-else 类型检查替换 vtable 调度,例如它看到只有几个替代方案(这可能是有利的,因为在这种情况下分支预测将起作用,但不适用于 vtables,并且还因为 if-else 分支可以被内联)。它可以对 vtable 中的方法进行重新排序,以便最常调用的方法更早出现,或者使通常被调用的方法一个接一个地填充 vtable 中的相邻槽以利用缓存。等等等等。当然,所有这些实现也会使 vtable 布局完全不可预测,因此如果它被暴露(通过语言规范)实现。
同样,vtable 并不像听起来那么简单。例如,编译器通常必须生成 thunk 来修复 this
指针,用于虚拟继承或与协变返回类型相结合的多重继承。这又是一个没有“单一最佳方式”的事情(这就是为什么不同的编译器会做不同的事情),并且标准化它实际上需要采用特定的方式。
也就是说,“vtable 切换”是一种潜在有用的技术,如果公开为更高级别的构造(因此仍然可以进行优化)。例如,请参阅 UnrealScript,它允许为一个类定义多个 states(一个默认值,另一个命名),并覆盖命名状态中的一些方法。派生类可以覆盖现有状态中的更多方法,或者添加自己的状态并在其中覆盖。此外,状态可以扩展其他状态(因此,如果一个方法没有被特定状态覆盖,它会回退到“父”状态,依此类推,直到链达到默认状态)。对于演员建模(本质上是游戏),这一切都很有意义,这就是 UnrealScript 拥有它的原因。而所有这一切的明显有效的实现机制是 vtable 切换,每个状态都有一个单独的 vtable。
【讨论】:
【参考方案5】:JavaScript、Python 和 Ruby 都可以做到这一点。在这些语言中,类和实例定义在运行时是可变的。抽象地说,每个对象和类型都是可以检查和更新的成员变量和方法的字典。
这在 C++ 中是不可能的,因为它需要能够重写生成的二进制代码,这会带来巨大的性能成本。
【讨论】:
【参考方案6】:Vtables 仅存在于某些编译器的特定情况下(即它们没有在标准中指定,而是在实现细节中指定)。即使它们确实存在,它们也只会在您拥有虚函数并且需要间接实现多态性时才会出现。如果不需要,可以对其进行优化,从而节省调用间接的开销。
遗憾的是(或其他情况,取决于您对此事的看法;-),C++ 并非旨在支持猴子补丁。在某些情况下(例如 COM),vtable 是实现的一部分,您也许可以在幕后进行讨论。但是,这永远不会被支持或移植。
【讨论】:
【参考方案7】:我相信你可以用 Python 等动态语言来做类似的事情:
>>> class X():
... def bar(self): print "bar"
...
>>> x = X()
>>> x.bar()
bar
>>> def foo(x): print "foo"
...
>>> X.bar = foo
>>> x.bar()
foo
与 C++ 等静态语言的不同之处在于,解释器在运行时查找所有名称,然后决定要做什么。
在 C++ 中,“用另一个成员函数替换”问题可能还有其他解决方案,其中最简单的可能是使用函数指针:
#include <iostream>
class X;
typedef void (*foo_func)(const X&);
void foo(const X&) std::cout << "foo\n";
void bar(const X&) std::cout << "bar\n";
class X
foo_func f;
public:
X(): f(foo)
void foobar() f(*this);
void switch_function(foo_func new_foo) f = new_foo;
;
int main()
X x;
x.foobar();
x.switch_function(bar);
x.foobar();
(foo 和 bar 不使用 X& 参数,在此示例中,类似于 Python 示例)
【讨论】:
【参考方案8】:我正在研究一种公开 vtable 的静态编译语言,相信我要公开它是相当多的 hair。
【讨论】:
【参考方案9】: 您可以在 C++ 中执行的任何操作,都可以在直接的 C 中执行,但需要一点麻烦。 任何适度合理的 C 程序都应该用 C++ 编译。也许您想要的是在不使用 C++ 内置工具的情况下实现自己的 vtable。使用指向成员函数 (ptmf) 的指针会很有趣!
您将很难找到带有 vtable 内省的编译语言,因为需求很少且不易实现。但是,对于解释型语言,情况正好相反。
【讨论】:
【参考方案10】:还有其他语言吗 那里允许我这样做 东西?
Objective-C(以及 Objective-C++)允许运行时替换已编译的方法。它要么是静态和动态技术的最佳组合,要么是最差的,这取决于你问谁。
【讨论】:
【参考方案11】:正如其他人所指出的,C++ 标准中没有“vtable”的概念,因为它只是一种几乎通用的实现技术,很像名称修饰。
如果您希望能够在编译语言中动态地重新定义函数,您可能会对 Common Lisp 感兴趣。肯定有其他语言,但我能想到的唯一其他语言要么具有静态继承和函数,要么以巨大的性能成本进行解释。
【讨论】:
以上是关于为啥 C++ 继承机制不透明?的主要内容,如果未能解决你的问题,请参考以下文章