纯虚函数调用有趣的案例

时间:2014-09-08 11:17:28

标签: c++ inheritance compilation virtual-functions pure-virtual

请考虑以下代码:

#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

有人可以解释这两种情况的行为吗?

4 个答案:

答案 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():

  • 案例-1:A&c; ctor调用A-ctor =&gt; f()直接。编译器确切地知道情况并决定发出警告。
  • 案例2:A&c; ctor调用A-ctor =&gt; g()=&gt; F()。完全合法的情况是从一个类方法中调用f()。编译器不能说这是非法的。调用图可能来自* =&gt; bar()=&gt; g() - &gt; f(),表示对象的类型未知。这样的路径是可能的,这使得动态调度成为必要 - 导致运行时错误。

正如其他人指出的那样,这是未定义的用法,编译器只在检测和警告方面走得太远。

答案 3 :(得分:0)

Undefined行为意味着编译器不必以任何特别定义的方式处理该情况。

在这里,您的编译器知道其构造函数中的实际A类型能够内联纯虚方法,而不是通过v-table调用它。如果方法是正常的虚拟,而不是纯虚拟,那将会发生这种情况,这将是定义的行为。

虽然通过g()的行为也是如此,但编译器并没有为纯虚拟f()函数执行此操作。它没有必要。

简单的道德是不调用未定义的行为,如果你想从构造函数中调用f(),不要使它成为纯虚拟的。

如果要强制执行子类以实现f(),请不要从A的构造函数中调用它,但要为该函数调用另一个名称。最好不要虚拟。