来自 Base Ctor 的纯虚函数调用
考虑以下示例代码:
#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
、Line3
和 Line4
。
我明白:Line1
给出编译错误,因为无法从ctor调用纯虚函数。
问题:
为什么
Line2
和Line4
没有给出编译错误
,原因与中提到的相同>我明白
上面的陈述吗?Line2
和Line4
中的调用最终只会导致linker-error
。为什么
Line3
既不给出编译错误,也不给出链接器错误,而只给出运行时异常
?
通过构造函数调用纯虚函数时 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:
Why does
Line2
andLine4
give nocompilation error
for the same reason mentioned inI understand
statement above?. The calls inLine2
andLine4
will eventually causelinker-error
only.Why does
Line3
give neither compilation error nor linker error but givesrun-time exception
only ?
Real-Life example of UB when Pure virtual function call through constructor:
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
在所有四种情况下,行为都是未定义的;所以到底会发生什么取决于你的编译器在面对无效输入时会做什么。
编译器可能会尝试诊断问题并发出警告;这对于第 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()
, orbptr->Base::bar()
. Calling it dynamically would still give undefined behaviour.从构造函数调用纯虚函数是一种未定义行为 &编译器可以自由地显示任何行为。
参考:
C++03 标准 10.4/6:
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:
The C++ standard defines Undefined behavior in:
[defns.undefined] 1.3.12 undefined behavior
我可以部分回答。第 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.
您使用哪个编译器?
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.
奇怪的 C++ 事实:定义纯虚函数是合法的(而且很少有用)。
你可以尝试添加
,你会发现第 2 行和第 4 行调用了该函数。
调用该定义的合法方法是显式执行:
base::bar();
在这些情况下,编译器已设法将虚拟调用(将失败)优化为非虚拟调用。既没有要求也没有阻止这样做;您的所有调用都有未定义的行为。
Weird C++ fact: it is legal (and, very rarely, useful) to define a pure virtual function.
You could try adding
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.
您的代码包含未定义的行为,因此无论编译器做什么
正确的。代码中对
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 dynamicresolution, and would result in a call to a pure virtual function; this
is undefined behavior. (You can call
Base::bar()
if you write it likethat 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.