来自Base Ctor的Pure Virtual Function调用

时间:2012-02-09 14:01:00

标签: c++ constructor linker-errors runtime-error pure-virtual

请考虑以下示例代码:

#include <iostream>

using namespace std;

class base
{
   public:
      base()
      {
         bar(); //Line1
         this->bar(); //Line2
         base *bptr = this; 
         bptr->bar(); //Line3
         ((base*)(this))->bar(); //Line4
      }

      virtual void bar() = 0;
};

class derived: base
{
   public:
      void bar()
      {
         cout << "vfunc in derived class\n";
      }
};

int main()
{
   derived d;
}

上面的代码在基类中具有纯虚函数bar(),它在派生类中被覆盖。纯虚函数bar()在基类中没有定义。

现在关注Line1Line2Line3Line4

我理解Line1会出现编译错误,因为无法从ctor调用纯虚函数。

问题:

  1. 为什么Line2Line4出于compilation error语句中提到的相同原因而没有给出I understandLine2Line4中的来电最终只会导致linker-error

  2. 为什么Line3既没有编译错误也没有链接器错误,但只提供run-time exception

  3. 纯虚函数通过构造函数调用时UB的Real-Life示例:

    Real-Life example of UB when Pure virtual function call through constructor

6 个答案:

答案 0 :(得分:6)

在所有四种情况下,行为都是未定义的;所以究竟发生了什么取决于你的编译器在无效输入面前做了什么。

编译器可能会尝试诊断问题以发出警告;对于第1行来说这很容易做到,而对于其他行来说则更难,这可以解释为什么你只看到第1行的警告。

当从构造函数调用虚函数时,编译器知道应该调用哪个重载,因此它可能会生成静态调用。这就是您从第2行和第4行收到链接错误的原因。

在第3行中,编译器必须已经确定它是否太难以确定是否可以生成静态调用,因此它生成了动态调用。跟踪变量的值比确定临时指针必须引用this更难,而且通常根本不可能。这就是你在那里遇到运行时错误的原因。

当然,所有这些都是未定义的行为,可能会从编译器变为编译器,或者根据月亮的相位而变化。

如果该函数有实现,那么将其称为静态Base::bar()bptr->Base::bar()是有效的。动态调用它仍然会给出未定义的行为。

答案 1 :(得分:5)

从构造函数调用纯虚函数是未定义行为&amp;编译器可以自由地显示任何行为。

参考:
C ++ 03标准10.4 / 6:

  

“可以从抽象类的构造函数(或析构函数)调用成员函数;对于正在创建(或销毁)的对象,直接或间接地对纯虚函数进行虚拟调用(10.3)的效​​果构造函数(或析构函数)未定义。“

C ++标准定义了未定义的行为:

[defns.undefined] 1.3.12未定义的行为

  

行为,例如在使用错误的程序结构或错误数据时可能出现的行为,本国际标准没有规定任何要求。当本国际标准忽略对行为的任何明确定义的描述时,也可能预期未定义的行为。 [注意:允许的未定义行为包括完全忽略不可预测的结果,在翻译或程序执行期间以环境特征的文件形式(有或没有发出诊断消息)行为,终止翻译或执行(发出诊断信息)。许多错误的程序结构不会产生未定义的行为;他们需要被诊断出来。]

答案 2 :(得分:1)

我可以部分回答。第3行要求编译器进行数据流分析,以确定函数未在另一个完全构造的对象上被调用。

答案 3 :(得分:1)

您使用的是哪种编译器?

Vc10和gcc 4.6都编译好了。 gcc给出一个关于从构造函数调用虚函数的好警告,只是使用base :: bar()函数而不是多态。

这可能是从构造函数调用虚拟的bocs是Undefined Behavior。

答案 4 :(得分:0)

奇怪的C ++事实:定义纯虚函数是合法的(并且很少有用)。

您可以尝试添加

void base::bar() { cout << "Wuh?"; }

你会发现第2行和第4行调用了这个函数。

调用该定义的合法方式是明确地执行此操作:base::bar();

在这些情况下,编译器已设法优化虚拟调用(将失败)到非虚拟调用。既不要求也不阻止这样做;你所有的电话都有不确定的行为。

答案 5 :(得分:0)

您的代码包含未定义的行为,因此无论编译器做什么 正确。您代码中对bar()的所有调用都需要动态 分辨率,并将导致调用纯虚函数;这个 是未定义的行为。 (如果您这样写,可以致电Base::bar() 那个和函数存在,因为不需要动态分辨率。) 代码编译的事实并不意味着它将运行 成功;例如,在g ++的情况下,我很确定它 将崩溃并显示错误消息。

编译器是否抱怨可能取决于多少 努力在编译时解决动态解决方案。 没有优化,它几乎肯定无法在编译时解析3 时间,但我有点惊讶它以不同的方式对待1和2。

并且声明“无法调用纯虚函数 构造函数“是假的。唯一有问题的是动态时 分辨率解析为纯虚函数。用静态调用它 分辨率(假设它存在)很好,并调用纯虚拟 如果动态分辨率出现a,基类中的函数就可以了 构造函数具有的派生类中的非纯虚函数 开始或已经开始。