来自 Base Ctor 的纯虚函数调用

发布于 2025-01-04 00:13:09 字数 1179 浏览 0 评论 0原文

考虑以下示例代码:

#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没有给出编译错误,原因与中提到的相同>我明白上面的陈述吗? Line2Line4 中的调用最终只会导致 linker-error

  2. 为什么 Line3 既不给出编译错误,也不给出链接器错误,而只给出运行时异常

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

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

Consider the following sample code:

#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;
}

The above code has pure virtual function bar() in base class which is overriden in the derived class. The pure virtual function bar() has no definition in base class.

Now focus on Line1, Line2, Line3 and Line4.

I understand : Line1 gives compilation error, because pure virtual function cannot be called from ctor.

Questions:

  1. Why does Line2 and Line4 give no compilation error for the same reason mentioned in I understand statement above?. The calls in Line2 and Line4 will eventually cause linker-error only.

  2. Why does Line3 give neither compilation error nor linker error but gives run-time exception only ?

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

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

温柔女人霸气范 2025-01-11 00:13:09

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

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

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

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

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

如果该函数有一个实现,那么静态地调用它是有效的,如 Base::bar()bptr->Base::酒吧()。动态调用它仍然会产生未定义的行为。

In all four cases, the behaviour is undefined; so exactly what happens depends on what your compiler happens to do in the face of invalid input.

The compiler might attempt to diagnose the problem to give a warning; this is easy to do for Line 1, and more difficult for the other lines, which would explain why you only see a warning for Line 1.

When calling a virtual function from a constructor, the compiler knows which overload should be called, and so it might generate a static call. This is why you get a link error from Line 2 and Line 4.

In Line 3, the compiler must have decided that it's too difficult to work out whether it can generate a static call, so it generated a dynamic call instead. Tracking the value of a variable is rather harder than working out that a temporary pointer must refer to this, and often not possible at all. That's why you get a run-time error there.

Of course, all of this is undefined behaviour, and might change from compiler to compiler, or according to the phase of the moon.

If the function had an implementation, then it would be valid to call it statically, as Base::bar(), or bptr->Base::bar(). Calling it dynamically would still give undefined behaviour.

无法言说的痛 2025-01-11 00:13:09

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

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

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

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

[defns.undefined] 1.3.12 未定义行为

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

Calling an Pure virtual function from constructor is an Undefined Behavior & the compiler is free to show any behavior.

Reference:
C++03 Standard 10.4/6:

"Member functions can be called from a constructor (or destructor) of an abstract class; the effect of making a virtual call (10.3) to a pure virtual function directly or indirectly for the object being created (or destroyed) from such a constructor (or destructor) is undefined."

The C++ standard defines Undefined behavior in:

[defns.undefined] 1.3.12 undefined behavior

behavior, such as might arise upon use of an erroneous program construct or erroneous data, for which this International Standard imposes no requirements. Undefined behavior may also be expected when this International Standard omits the description of any explicit definition of behavior. [Note: permissible undefined behavior ranges from ignoring the situation completely with unpredictable results, to behaving during translation or program execution in a documented manner characteristic of the environment (with or without the issuance of a diagnostic message), to terminating a translation or execution (with the issuance of a diagnostic message). Many erroneous program constructs do not engender undefined behavior; they are required to be diagnosed. ]

ゃ人海孤独症 2025-01-11 00:13:09

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

I can partially answer. Line 3 requires that the compiler do data flow analysis to determine that the function Is not being called on another fully constructed object.

绝不放开 2025-01-11 00:13:09

您使用哪个编译器?

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

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

Which compiler are you using?

Vc10 and gcc 4.6 all compile fine. gcc give a nice warning about calling virtual function from constructor will be just using the base::bar() function instead of polymorphic.

This is probably bocs calling virtual from constructor is Undefined Behavior.

水溶 2025-01-11 00:13:09

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

你可以尝试添加

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

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

调用该定义的合法方法是显式执行:base::bar();

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

Weird C++ fact: it is legal (and, very rarely, useful) to define a pure virtual function.

You could try adding

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

and you will find line 2 and line 4 invoke that function.

The legal way to call that definition is to do it explicitly: base::bar();

The compiler has managed to optimise the virtual call (which will fail) to the non-virtual call in those cases. It is neither required nor prevented from doing that; all your calls have undefined behaviour.

ペ泪落弦音 2025-01-11 00:13:09

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

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

并且声明“纯虚函数不能被调用”
构造函数”为 false。唯一出现问题的时候是动态的
解析解析为纯虚函数。用静态调用它
分辨率(假设存在)很好,并且调用纯虚拟
如果动态分辨率达到a,基类中的函数就很好
其构造函数具有的派生类中的非纯虚函数
已开始或已运行。

Your code contains undefined behavior, so whatever the compiler does is
correct. All of the calls to bar() in your code require dynamic
resolution, and would result in a call to a pure virtual function; this
is undefined behavior. (You can call Base::bar() if you write it like
that and the function exists, since no dynamic resolution is required.)
The fact that the code compiles doesn't mean that it will run
successfully; in the case of g++, for example, I'm fairly sure that it
will crash with an error message.

Whether the compiler complains or not probably depends on how much
effort it goes to to resolve the dynamic resolution at compile time.
Without optimization, it almost certainly can't resolve 3 at compile
time, but I'm somewhat surprised that it treats 1 and 2 differently.

And the statement that "pure virtual function cannot be called from
constructor" is false. The only time there is a problem is when dynamic
resolution resolves to a pure virtual function. Calling it with static
resolution (assuming it exists) is fine, and calling a pure virtual
function in a base class is fine if the dynamic resolution turns up a
non pure virtual function in a derived class whose constructor has
started or has run.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文