请考虑以下代码:
#include <iostream>
using namespace std;
class A
{
public:
virtual void f() = 0;
A(){f();}
};
void A::f() {
cout<<"A"<<endl;
}
class B:public A{
public:
void f(){cout<<"B"<<endl;}
};
int main()
{
B b;
}
在这种情况下,我直接从构造函数调用虚函数并获得编译器警告,其中说明:
警告:从构造函数调用抽象虚拟'虚拟空虚A :: f()'
但它会在没有终止的情况下执行并打印A。
如果我像这样包装函数的调用:
class A
{
public:
virtual void f() = 0;
A(){g();}
void g(){f();}
};
void A::f(){cout<<"A"<<endl;}
class B:public A{
public:
void f(){cout<<"B"<<endl;}
};
int main()
{
B b;
}
编译器在编译期间不会输出任何警告,但会在运行时使用以下消息进行压缩:
pure virtual method called
terminate called without active exception
Abort
有人可以解释这两种情况的行为吗?
答案 0 :(得分:1)
在第一种情况下,编译器碰巧通过静态分派到A::f()
来保存您,因为它知道A
的静态类型。但这是非常正确的,这是非常不明确的行为,你不应该这样做。
在第二种情况下,编译器不会静态调度到A::f()
,因为调用不在构造函数中,因此它必须动态调度它。不同的ABI以不同方式处理纯虚拟调用,但MSVC和Itanium都有一个专用的纯虚拟调用处理程序,它放在vtable中以捕获这些事件。这就是产生您看到的错误消息的原因。
答案 1 :(得分:1)
§10.4抽象类[class.abstract] / p6
可以从抽象类的构造函数(或析构函数)调用成员函数;将虚拟调用(10.3)直接发送到纯虚拟函数或间接对正在创建(或销毁)来自的对象的影响构造函数(或析构函数)未定义。
简而言之:未定义直接或间接调用从构造函数创建的对象的纯虚函数的效果。
无论是直接调用还是间接调用,都无法从构造函数或析构函数中调用纯虚拟成员函数,因为最终会出现未定义的行为。
提供纯虚函数实现的唯一有用示例是从派生类调用它时:
struct A
{
virtual void f() = 0;
};
void A::f()
{
cout<<"A"<<endl;
}
struct B : A
{
void f()
{
A::f();
cout<<"B"<<endl;
}
};
答案 2 :(得分:1)
从编译器的角度来看,如果你看看如何调用函数f():
正如其他人指出的那样,这是未定义的用法,编译器只在检测和警告方面走得太远。
答案 3 :(得分:0)
Undefined
行为意味着编译器不必以任何特别定义的方式处理该情况。
在这里,您的编译器知道其构造函数中的实际A类型能够内联纯虚方法,而不是通过v-table调用它。如果方法是正常的虚拟,而不是纯虚拟,那将会发生这种情况,这将是定义的行为。
虽然通过g()
的行为也是如此,但编译器并没有为纯虚拟f()
函数执行此操作。它没有必要。
简单的道德是不调用未定义的行为,如果你想从构造函数中调用f()
,不要使它成为纯虚拟的。
如果要强制执行子类以实现f()
,请不要从A的构造函数中调用它,但要为该函数调用另一个名称。最好不要虚拟。