我和一个朋友对对象的构造进行了一次非常有趣的讨论,最终得到了这段代码:
#include <iostream>
class Parent {
public:
Parent( ) {
this->doSomething( );
}
virtual void doSomething( ) = 0;
};
class Child : public Parent {
int param;
public:
Child( ) {
param = 1000;
}
virtual void doSomething( ) {
std::cout << "doSomething( " << param << " )" << std::endl;
}
};
int main( void ) {
Child c;
return 0;
}
我知道标准没有定义从构造函数或析构函数调用纯虚函数时的行为,这也不是我如何在生产中编写代码的实际示例,它只是检查编译器做什么的测试。
在 Java 打印中测试相同的构造
做某事(0)
这是有道理的,因为param
此时尚未初始化doSomething()
从父构造函数调用。
我期望 C++ 中有类似的行为,区别在于param
包含调用函数时的任何内容。
相反,编译上面的代码会导致链接器错误(c++ (Ubuntu 4.4.3-4ubuntu5.1) 4.4.3)
说的是参考Parent::doSomething( )
未定义。
所以,我的问题是:为什么这是链接器错误?如果这是一个错误,我希望编译器会抱怨,特别是因为有该函数的实现。
任何有关链接器在这种情况下如何工作的见解或对进一步阅读的参考都将受到高度赞赏。
先感谢您!我希望这个问题不是重复的,但我找不到类似的问题..
所以,我的问题是:为什么这是链接器错误?如果这是一个错误,我希望编译器会抱怨,特别是因为有该函数的实现。任何有关链接器在这种情况下如何工作的见解或对进一步阅读的参考都将受到高度赞赏。
让我们进一步扩展一下。
为什么是链接器错误?
因为编译器注入了一个调用Parent::doSomething()
来自构造函数,但链接器发现没有该函数的定义。
我希望编译器会抱怨,特别是因为有该函数的实现。
这是不正确的。有一个override对于可以通过虚拟调度访问的函数,但是该函数Parent::doSomething()
没有定义。这里有一个微妙但重要的区别,可以通过禁用动态调度以不同的方式进行测试。您可以通过使用类名限定函数来禁用特定调用的动态调度,例如,Child::doSomething()
如果你添加Parent::doSomething()
,这将生成一个调用Parent::doSomething()
不使用动态调度来调用最终的重写器。
为什么这很重要?
这很重要,因为即使函数是纯虚拟(纯虚拟意味着动态调度永远不会调度到特定的重载),它也可以是defined and called:
struct base {
virtual void f() = 0;
};
inline void base::f() { std::cout << "base\n"; }
struct derived : base {
virtual void f() {
base::f();
std::cout << "derived\n";
}
};
int main() {
derived d;
d.f(); // outputs: base derived
}
现在,C++ 有一个单独的编译模型,这意味着不需要在这个特定的翻译单元中定义函数。那是Parent::doSomething()
可以在链接到同一程序的不同翻译单元中定义。编译器不可能知道任何其他 TU 是否会定义该函数,只有链接器知道,因此是链接器抱怨的。
任何有关链接器在这种情况下如何工作的见解或对进一步阅读的参考都将受到高度赞赏。
如前所述,编译器(这个特定的编译器)正在添加对特定覆盖的调用Parent
等级。与所有其他函数调用一样,链接器试图查找任何翻译单元中定义的符号,但失败,从而触发错误。
The 纯虚拟说明符的唯一目的是避免隐式使用 (odr-use在标准中)创建虚拟表时的函数。也就是说,纯说明符的唯一目的不是向虚拟表添加对该符号的依赖关系。这反过来意味着链接器不需要符号的存在(Parent::doSomething
)在程序中用于动态调度(vtable),但如果程序显式使用它,它仍然需要该符号。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)