首页 文章

从基类构造函数调用纯虚函数

提问于
浏览
32

我有一个包含纯虚函数的基类MyBase:

void PrintStartMessage() = 0

我希望每个派生类在它们的构造函数中调用它

然后我把它放在基类( MyBase )构造函数中

class MyBase
 {
 public:

      virtual void PrintStartMessage() =0;
      MyBase()
      {
           PrintStartMessage();
      }

 };

 class Derived:public MyBase
 {     

 public:
      void  PrintStartMessage(){

      }
 };

void main()
 {
      Derived derived;
 }

但我收到链接器错误 .

this is error message : 

 1>------ Build started: Project: s1, Configuration: Debug Win32 ------
 1>Compiling...
 1>s1.cpp
 1>Linking...
 1>s1.obj : error LNK2019: unresolved external symbol "public: virtual void __thiscall MyBase::PrintStartMessage(void)" (?PrintStartMessage@MyBase@@UAEXXZ) referenced in function "public: __thiscall MyBase::MyBase(void)" (??0MyBase@@QAE@XZ)
 1>C:\Users\Shmuelian\Documents\Visual Studio 2008\Projects\s1\Debug\s1.exe : fatal error LNK1120: 1 unresolved externals
 1>s1 - 2 error(s), 0 warning(s)

我想强制所有派生类来...

A- implement it

B- call it in their constructor

我该怎么做?

6 回答

  • 5

    在该对象仍在构造时尝试从派生中调用纯抽象方法是不安全的 . 这就像试图将汽油填充到汽车中,但是那辆汽车仍在装配线上并且还没有放入油箱 .

    你可以做的最接近的事情就是首先完全构造你的对象,然后在之后调用方法:

    template <typename T>
    T construct_and_print()
    {
      T obj;
      obj.PrintStartMessage();
    
      return obj;
    }
    
    int main()
    {
        Derived derived = construct_and_print<Derived>();
    }
    
  • 0

    您无法以您想象的方式执行此操作,因为您无法从基类构造函数中调用派生的虚函数 - 该对象尚未属于派生类型 . 但你不需要这样做 .

    在MyBase构造之后调用PrintStartMessage

    我们假设你想做这样的事情:

    class MyBase {
    public:
        virtual void PrintStartMessage() = 0;
        MyBase() {
            printf("Doing MyBase initialization...\n");
            PrintStartMessage(); // ⚠ UB: pure virtual function call ⚠
        }
    };
    
    class Derived : public MyBase {
    public:
        virtual void PrintStartMessage() { printf("Starting Derived!!!\n"); }
    };
    

    所需的执行跟踪将是:

    Doing MyBase initialization...
    Starting Derived!!!
    

    但这就是构造函数的用途!只是废弃虚函数并使派生的构造函数完成工作!

    class MyBase {
    public:
        MyBase() { printf("Doing MyBase initialization...\n"); }
    };
    
    class Derived : public MyBase {
    public:
        Derived() { printf("Starting Derived!!!\n"); }
    };
    

    输出就是我们所期望的:

    Doing MyBase initialization...
    Starting Derived!!!
    

    但是,这并不强制派生类显式实现 PrintStartMessage 功能 . 但另一方面,请三思而后行是否有必要,因为无论如何它们总是可以提供空的实现 .

    在MyBase构造之前调用PrintStartMessage

    如上所述,如果要在 Derived 构造函数之前调用 PrintStartMessage ,则无法完成此操作,因为还没有 Derived 对象可以调用 PrintStartMessage . 要求 PrintStartMessage 成为非静态成员是没有意义的,因为它无法访问任何 Derived 数据成员 .

    具有工厂功能的静态功能

    或者我们可以使它像一个静态成员:

    class MyBase {
    public:
        MyBase() {
            printf("Doing MyBase initialization...\n");
        }
    };
    
    class Derived : public MyBase {
    public:
        static void PrintStartMessage() { printf("Derived specific message.\n"); }
    };
    

    一个自然的问题是它将如何被称为?

    我可以看到两种解决方案:一种类似于@greatwolf,你必须手动调用它 . 但是现在,因为它是一个静态成员,所以你可以在构造 MyBase 的实例之前调用它:

    template<class T>
    T print_and_construct() {
        T::PrintStartMessage();
        return T();
    }
    
    int main() {
        Derived derived = print_and_construct<Derived>();
    }
    

    输出将是

    Derived specific message.
    Doing MyBase initialization...
    

    此方法会强制所有派生类实现 PrintStartMessage . 不幸的是,只有当我们用我们的工厂功能构建它们时才会这样......这是这个解决方案的一个巨大缺点 .

    第二种解决方案是采用奇怪的重复模板模式(CRTP) . 通过在编译时告诉 MyBase 完整的对象类型,它可以在构造函数中进行调用:

    template<class T>
    class MyBase {
    public:
        MyBase() {
            T::PrintStartMessage();
            printf("Doing MyBase initialization...\n");
        }
    };
    
    class Derived : public MyBase<Derived> {
    public:
        static void PrintStartMessage() { printf("Derived specific message.\n"); }
    };
    

    输出符合预期,无需使用专用工厂功能 .

    使用CRTP从PrintStartMessage中访问MyBase

    MyBase 正在执行时,它已经可以访问其成员 . 我们可以让 PrintStartMessage 能够访问调用它的 MyBase

    template<class T>
    class MyBase {
    public:
        MyBase() {
            T::PrintStartMessage(this);
            printf("Doing MyBase initialization...\n");
        }
    };
    
    class Derived : public MyBase<Derived> {
    public:
        static void PrintStartMessage(MyBase<Derived> *p) {
            // We can access p here
            printf("Derived specific message.\n");
        }
    };
    

    以下也是有效且经常使用的,虽然有点危险:

    template<class T>
    class MyBase {
    public:
        MyBase() {
            static_cast<T*>(this)->PrintStartMessage();
            printf("Doing MyBase initialization...\n");
        }
    };
    
    class Derived : public MyBase<Derived> {
    public:
        void PrintStartMessage() {
            // We can access *this member functions here, but only those from MyBase
            // or those of Derived who follow this same restriction. I.e. no
            // Derived data members access as they have not yet been constructed.
            printf("Derived specific message.\n");
        }
    };
    

    没有模板解决方案 - 重新设计

    还有一种选择是重新设计你的代码 . IMO这个实际上是首选的解决方案,如果你必须从 MyBase 构造中调用一个被覆盖的 PrintStartMessage .

    此提案将 DerivedMyBase 分开,如下所示:

    class ICanPrintStartMessage {
    public:
        virtual ~ICanPrintStartMessage() {}
        virtual void PrintStartMessage() = 0;
    };
    
    class MyBase {
    public:
        MyBase(ICanPrintStartMessage *p) : _p(p) {
            _p->PrintStartMessage();
            printf("Doing MyBase initialization...\n");
        }
    
        ICanPrintStartMessage *_p;
    };
    
    class Derived : public ICanPrintStartMessage {
    public:
        virtual void PrintStartMessage() { printf("Starting Derived!!!\n"); }
    };
    

    您初始化 MyBase 如下:

    int main() {
        Derived d;
        MyBase b(&d);
    }
    
  • 14

    您不应该在构造函数中调用 virtual 函数 . Period . 你必须找到一些解决方法,比如制作 PrintStartMessagevirtual 并在每个构造函数中显式调用 .

  • 7

    如果PrintStartMessage()不是纯虚函数而是普通虚函数,编译器就不会抱怨它 . 但是你仍然需要弄清楚派生的原因未调用PrintStartMessage()的版本 .

    由于派生类在其自己的构造函数之前调用基类的构造函数,因此派生类的行为类似于基类,因此调用基类的函数 .

  • 0

    有很多文章解释了为什么你永远不应该在构造函数和析构函数中调用虚函数 . 看一下herehere,了解在此类调用过程中幕后发生的情况 .

    简而言之,对象是从基础到派生的构造的 . 因此,当您尝试从基类构造函数调用虚函数时,尚未发生从派生类重写,因为尚未调用派生构造函数 .

  • 30

    我知道这是一个老问题,但在处理我的程序时我遇到了同样的问题 .

    如果您的目标是通过让Base类处理共享初始化代码同时要求Derived类在纯虚方法中指定它们唯一的代码来减少代码重复,那么这就是我所决定的 .

    #include <iostream>
    
    class MyBase
    {
    public:
        virtual void UniqueCode() = 0;
        MyBase() {};
        void init(MyBase & other)
        {
          std::cout << "Shared Code before the unique code" << std::endl;
          other.UniqueCode();
          std::cout << "Shared Code after the unique code" << std::endl << std::endl;
        }
    };
    
    class FirstDerived : public MyBase
    {
    public:
        FirstDerived() : MyBase() { init(*this); };
        void  UniqueCode()
        {
          std::cout << "Code Unique to First Derived Class" << std::endl;
        }
    private:
        using MyBase::init;
    };
    
    class SecondDerived : public MyBase
    {
    public:
        SecondDerived() : MyBase() { init(*this); };
        void  UniqueCode()
        {
          std::cout << "Code Unique to Second Derived Class" << std::endl;
        }
    private:
        using MyBase::init;
    };
    
    int main()
    {
        FirstDerived first;
        SecondDerived second;
    }
    

    输出是:

    Shared Code before the unique code
     Code Unique to First Derived Class
     Shared Code after the unique code
    
     Shared Code before the unique code
     Code Unique to Second Derived Class
     Shared Code after the unique code
    

相关问题