5

考虑以下示例代码:

#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()在基类中没有定义。

现在关注Line1,Line2和。Line3Line4

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

问题:

  1. 为什么Line2和上面声明中提到的相同原因不Line4给出?呼入和最终只会引起。compilation errorI understandLine2Line4linker-error

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

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

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

4

6 回答 6

6

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

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

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

在第 3 行中,编译器一定认为很难确定它是否可以生成静态调用,因此它生成了动态调用。跟踪变量的值比找出临时指针必须引用的要困难得多this,而且通常根本不可能。这就是为什么你会在那里得到一个运行时错误。

当然,所有这些都是未定义的行为,并且可能会因编译器而异,或者根据月相而异。

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

于 2012-02-09T14:10:50.160 回答
5

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

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

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

C++ 标准在以下方面定义了未定义的行为:

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

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

于 2012-02-09T14:12:23.277 回答
1

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

于 2012-02-09T14:11:42.857 回答
1

您使用的是哪个编译器?

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

这可能是 bocs 从构造函数调用 virtual 是未定义的行为。

于 2012-02-09T14:17:48.337 回答
0

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

你可以尝试添加

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

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

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

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

于 2012-02-09T14:13:49.170 回答
0

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

编译器是否抱怨可能取决于它在编译时解决动态分辨率的工作量。如果没有优化,它几乎肯定无法在编译时解析 3,但我有点惊讶它对 1 和 2 的处理方式不同。

而“不能从构造函数调用纯虚函数”的说法是错误的。唯一出现问题的情况是动态解析解析为纯虚函数。使用静态解析调用它(假设它存在)很好,如果动态解析在其构造函数已启动或已运行的派生类中出现非纯虚函数,则在基类中调用纯虚函数也很好。

于 2012-02-09T14:16:08.367 回答