【问题标题】:Invoking virtual function and pure-virtual function from a constructor从构造函数调用虚函数和纯虚函数
【发布时间】:2012-01-28 08:47:39
【问题描述】:

当我从基本构造函数调用虚函数时,编译器不会给出任何错误。但是当我从基类构造函数调用纯虚拟函数时,它会给出编译错误。

考虑下面的示例程序:

#include <iostream>

using namespace std;
class base
{
   public:
      void virtual virtualfunc() = 0;
      //void virtual virtualfunc();
      base()
      {
         virtualfunc();
      }
};

void base::virtualfunc()
{
   cout << " pvf in base class\n";
}

class derived : public base
{
   public:
   void virtualfunc()
   {
      cout << "vf in derived class\n";
   }
};

int main()
{
   derived d;
   base *bptr = &d;
   bptr->virtualfunc();

   return 0;
}

这里可以看出纯虚函数是有定义的。我希望在执行bptr-&gt;virtualfunc() 时调用基类中定义的纯虚函数。相反,它给出了编译错误:

错误:调用了抽象虚拟`virtual void base::virtualfunc()' 来自构造函数

这是什么原因?

【问题讨论】:

    标签: c++ constructor compiler-errors virtual-functions dynamic-binding


    【解决方案1】:

    不要从构造函数调用纯虚函数,因为它会导致未定义的行为

    C++03 10.4/6 状态

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

    你得到一个编译错误,因为你没有在基类中定义纯虚函数virtualfunc()。为了能够调用它,它必须有一个身体。

    无论如何,应该避免在构造函数中调用纯虚函数,因为这样做是未定义的行为。

    【讨论】:

    • @Als:我已经定义了纯虚函数。不过,为什么会出现编译错误?
    • @LinuxPenseur:你确定你编译了正确的代码吗?在gcc-4.3.4 Without defintion 有错误但With definition 没有错误,但没有错误并不意味着它是正确的,它仍然是未定义的行为。
    • @LinuxPenseur:根据标准,它是未定义的行为,它是一种实现质量,可以告诉您它可能在运行时崩溃。如果您真的想从构造函数调用该函数,请禁用虚拟调度机制:base::virtualfunc(),这应该可以消除错误(同时在用户代码中明确说明您正在调用的内容。请注意,在某些编译器中,有问题的代码实际上无论如何都会调用base::virtualfunc()(即使是纯虚拟的,只要它被定义)而不会崩溃,但这只是UB的不同版本。
    • @Als: 你得到一个编译错误,因为你没有定义纯虚函数 一般来说,你会得到一个 link 错误未定义的功能。在这种情况下,编译器正在对 UB 的含义执行黑魔法。对于该编译器,UB 意味着如果函数已知则执行静态调度,或者如果函数未知则编译失败。我不太喜欢这种方法,就好像你将定义移到一个单独的 TU 中,那么你有两个完全相同的程序,其中一个可以编译并运行,而另一个甚至无法编译。
    • @DavidRodríguez-dribeas:Agreed.Compiler 在这里施展了魔法。 一般来说,对于未定义的函数,您会收到链接错误在正常或非纯虚函数的情况下为真,但根据标准,允许在没有定义的情况下存在纯虚函数(这是在此之前几分钟的 OP 的 Q)。我想说这是奇怪但允许的行为,也许不是用恰当的话。感谢您对此进行澄清。您的回复总是简洁明了。
    【解决方案2】:

    您应该记住,当您在基类构造函数中时,没有派生类。更多信息:

    http://www.parashift.com/c++-faq-lite/strange-inheritance.html#faq-23.5

    当你试图调用纯虚函数时,它还没有实现。

    有很多解决方案。最简单的方法是创建另一个成员函数 您将在基类的构造函数之后调用的“init()”。

    【讨论】:

    • 是的,有办法解决这个问题。两阶段初始化不是是一个不错的选择。更喜欢 PIMP 模式。
    【解决方案3】:

    编译器不需要假设在构造函数完成之前已经为纯虚函数设置了指针。换句话说,不需要知道此时您有该函数的定义。因此行为是未定义的。在某些编译器 (MSVC) 上,它会按预期工作,而在其他编译器上,它会给您当前遇到的错误。在其他一些上它会编译,但你会得到一个分段错误。

    无论如何,从构造函数中调用任何虚函数确实是个坏主意,因为这会使代码的意图变得模糊不清。

    【讨论】:

      【解决方案4】:

      在 C++ 11 中,有一个变通方法。

      在构造器委托的过程中,实际上你可以调用一个纯虚方法的实现——只要实现该纯虚方法的类的构造函数中至少有一个在纯虚之前已经完成调用被调用。

      您可以/应该使用“final”关键字以确保子类的行为不可预测。

      见:C++ 11 Delegated Constructor Pure Virtual Method & Function Calls — Dangers?

      #include <string>
      
      /**************************************/
      class Base
      {
      public:
          int sum;
          virtual int Do() = 0;
      
          void Initialize()
          {
              Do();
          }
          Base()
          {
          }
      };
      
      /**************************************/
      // Optionally declare class as "final" to avoid
      // issues with further sub-derivations.
      class Derived final : public Base
      {
      public:
      
          virtual int Do() override final
          {
              sum = 0 ? 1 : sum;
              return sum / 2 ; // .5 if not already set.
          }
      
          Derived(const std::string & test)
              : Derived() // Ensure "this" object is constructed.
          {
              Initialize(); // Call Pure Virtual Method.
          }
          Derived()
              : Base()
          {
              // Effectively Instantiating the Base Class.
              // Then Instantiating This.
              // The the target constructor completes.
          }
      };
      
      
      
      
      /********************************************************************/
      int main(int args, char* argv[])
      {
          Derived d;
          return 0;
      }
      

      【讨论】:

        猜你喜欢
        • 2012-01-27
        • 2021-10-20
        • 2010-10-05
        • 2012-01-28
        • 1970-01-01
        • 2011-08-06
        相关资源
        最近更新 更多