来自Base Ctor的Pure Virtual Function调用

nit*_*ian 5 c++ constructor runtime-error linker-errors 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;
}
Run Code Online (Sandbox Code Playgroud)

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

现在专注于Line1,Line2,Line3Line4.

我理解:Line1给出编译错误,因为无法从ctor调用纯虚函数.

问题:

  1. 为什么Line2Line4没有给出compilation error在同样的理由I understand上面的语句?呼叫进入Line2Line4最终linker-error只会导致.

  2. 为什么Line3既不提供编译错误也不提供链接器错误但run-time exception只给出?

纯虚函数通过构造函数调用时UB的实时示例:

纯虚函数通过构造函数调用时UB的真实例子

Mik*_*our 6

在所有四种情况下,行为都是不确定的; 所以到底发生了什么取决于你的编译器在无效输入面前做了什么.

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

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

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

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

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

  • 第2行怎么样比较困难?事实上,我希望编译器在编译的早期阶段将第1行重写为第2行 - 几乎只要它绑定了名称`bar`. (2认同)

Alo*_*ave 5

从构造函数调用Pure虚函数是未定义的行为,编译器可以自由地显示任何行为.

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

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

C++标准在以下位置定义了未定义的行为:

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

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