简体   繁体   English

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

[英]call to pure virtual function from base class constructor

I have a base class MyBase that contains a pure virtual function:我有一个包含纯虚函数的基类 MyBase:

void PrintStartMessage() = 0

I want each derived class to call it in their constructor我希望每个派生类在它们的构造函数中调用它

then I put it in base class( MyBase ) constructor然后我把它放在基类( MyBase )构造函数中

 class MyBase
 {
 public:

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

 };

 class Derived:public MyBase
 {     

 public:
      void  PrintStartMessage(){

      }
 };

void main()
 {
      Derived derived;
 }

but I get a linker error.但我收到链接器错误。

 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)

I want force to all derived classes to...我想强制所有派生类...

A- implement it

B- call it in their constructor 

How I must do it?我必须怎么做?

There are many articles that explain why you should never call virtual functions in constructor and destructor in C++.有很多文章解释了为什么在 C++ 中永远不应该在构造函数和析构函数中调用虚函数。 Take a look here and here for details what happens behind the scene during such calls.请查看此处此处了解在此类调用期间幕后发生的情况的详细信息。

In short, objects are constructed from the base up to the derived.简而言之,对象是从基础构建到派生的。 So when you try to call a virtual function from the base class constructor, overriding from derived classes hasn't yet happened because the derived constructors haven't been called yet.因此,当您尝试从基类构造函数调用虚函数时,尚未发生从派生类的覆盖,因为尚未调用派生构造函数。

Trying to call a pure abstract method from a derived while that object is still being constructed is unsafe.在该对象仍在构造时尝试从派生对象调用纯抽象方法是不安全的。 It's like trying to fill gas into a car but that car is still on the assembly line and the gas tank hasn't been put in yet.这就像试图给汽车加油,但那辆车仍在装配线上,而油箱尚未放入。

The closest you can get to doing something like that is to fully construct your object first and then calling the method after:最接近于做这样的事情是首先完全构造你的对象,然后在之后调用方法:

template <typename T>
T construct_and_print()
{
  T obj;
  obj.PrintStartMessage();

  return obj;
}

int main()
{
    Derived derived = construct_and_print<Derived>();
}

You can't do it the way you imagine because you cannot call derived virtual functions from within the base class constructor—the object is not yet of the derived type.你不能像你想象的那样做,因为你不能从基类构造函数中调用派生的虚函数——对象还不是派生类型。 But you don't need to do this.但是你不需要这样做。

Calling PrintStartMessage after MyBase construction MyBase 构建后调用 PrintStartMessage

Let's assume that you want to do something like this:让我们假设你想做这样的事情:

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"); }
};

That is, the desired output is:也就是说,所需的输出是:

Doing MyBase initialization...
Starting Derived!

But this is exactly what constructors are for!但这正是构造函数的用途! Just scrap the virtual function and make the constructor of Derived do the job:只需废弃虚函数并让Derived的构造函数完成这项工作:

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

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

The output is, well, what we would expect:输出是我们所期望的:

Doing MyBase initialization...
Starting Derived!

This doesn't enforce the derived classes to explicitly implement the PrintStartMessage functionality though.但这并不强制派生类显式实现PrintStartMessage功能。 But on the other hand, think twice whether it is at all necessary, as they otherwise can always provide an empty implementation anyway.但另一方面,请三思是否有必要,否则无论如何它们总是可以提供一个空的实现。

Calling PrintStartMessage before MyBase construction在 MyBase 构建之前调用 PrintStartMessage

As said above, if you want to call PrintStartMessage before the Derived has been constructed, you cannot accomplish this because there is no yet a Derived object for PrintStartMessage to be called upon.如上所述,如果您想在构造Derived之前调用PrintStartMessage ,则无法完成此操作,因为还没有用于调用PrintStartMessageDerived对象。 It would make no sense to require PrintStartMessage to be a non-static member because it would have no access to any of the Derived data members.要求PrintStartMessage为非静态成员是没有意义的,因为它无法访问任何Derived数据成员。

A static function with factory function带有工厂函数的静态函数

Alternatively we can make it a static member like so:或者,我们可以将其设为静态成员,如下所示:

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

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

A natural question arises of how it will be called?一个自然的问题是如何称呼它?

There are two solution I can see: one is similar to that of @greatwolf, where you have to call it manually.我可以看到两种解决方案:一种类似于@greatwolf 的解决方案,您必须手动调用它。 But now, since it is a static member, you can call it before an instance of MyBase has been constructed:但是现在,由于它是一个静态成员,您可以在构造MyBase的实例之前调用它:

template<class T>
T print_and_construct() {
    T::PrintStartMessage();
    return T();
}

int main() {
    Derived derived = print_and_construct<Derived>();
}

The output will be输出将是

Derived specific message.
Doing MyBase initialization...

This approach does force all derived classes to implement PrintStartMessage .这种方法确实强制所有派生类实现PrintStartMessage Unfortunately it's only true when we construct them with our factory function... which is a huge downside of this solution.不幸的是,只有当我们用我们的工厂函数构造它们时才会如此……这是这个解决方案的一个巨大缺点。

The second solution is to resort to the Curiously Recurring Template Pattern (CRTP).第二种解决方案是求助于 Curiously Recurring Template Pattern (CRTP)。 By telling MyBase the complete object type at compile time it can do the call from within the constructor:通过在编译时告诉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"); }
};

The output is as expected, without the need of using a dedicated factory function.输出符合预期,无需使用专用的工厂函数。

Accessing MyBase from within PrintStartMessage with CRTP使用 CRTP 从 PrintStartMessage 中访问 MyBase

While MyBase is being executed, its already OK to access its members.在执行MyBase ,已经可以访问其成员了。 We can make PrintStartMessage be able to access the MyBase that has called it:我们可以让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");
    }
};

The following is also valid and very frequently used, albeit a bit dangerous:以下也是有效且经常使用的,尽管有点危险:

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");
    }
};

No templates solution—redesign无模板解决方案——重新设计

Yet another option is to redesign your code a little.另一种选择是稍微重新设计您的代码。 IMO this one is actually the preferred solution if you absolutely have to call an overridden PrintStartMessage from within MyBase construction.如果您绝对必须从MyBase构造中调用覆盖的PrintStartMessage ,IMO 这实际上是首选解决方案。

This proposal is to separate Derived from MyBase , as follows:这个提议是将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"); }
};

You initialize MyBase as follows:您按如下方式初始化MyBase

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

You shouldn't call a virtual function in a constructor.您不应该在构造函数中调用virtual函数。 Period .期间 You'll have to find some workaround, like making PrintStartMessage non- virtual and putting the call explicitly in every constructor.您必须找到一些解决方法,例如使PrintStartMessagevirtual并将调用显式放置在每个构造函数中。

If PrintStartMessage() was not a pure virtual function but a normal virtual function, the compiler would not complain about it.如果 PrintStartMessage() 不是纯虚函数而是普通虚函数,编译器不会抱怨它。 However you would still have to figure out why the derived version of PrintStartMessage() is not being called.但是,您仍然需要弄清楚为什么没有调用 PrintStartMessage() 的派生版本。

Since the derived class calls the base class's constructor before its own constructor, the derived class behaves like the base class and therefore calls the base class's function.由于派生类在其自己的构造函数之前调用基类的构造函数,因此派生类的行为与基类相似,因此调用基类的函数。

I know this is an old question, but I came across the same question while working on my program.我知道这是一个老问题,但我在处理我的程序时遇到了同样的问题。

If your goal is to reduce code duplication by having the Base class handle the shared initialization code while requiring the Derived classes to specify the code unique to them in a pure virtual method, this is what I decided on.如果您的目标是通过让 Base 类处理共享初始化代码同时要求派生类在纯虚方法中指定它们独有的代码来减少代码重复,这就是我的决定。

#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;
}

The output is:输出是:

 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

Facing the same problem, I imaginated a (not perfect) solution.面对同样的问题,我想出了一个(不完美的)解决方案。 The idea is to provide a certificate to the base class that the pure virtual init function will be called after the construction.这个想法是为基类提供一个证书,表明在构造之后会调用纯虚 init 函数。

class A
{
  private:
    static const int checkValue;
  public:
    A(int certificate);
    A(const A& a);
    virtual ~A();
    virtual void init() = 0;
  public:
    template <typename T> static T create();
    template <typeneme T> static T* create_p();
    template <typename T, typename U1> static T create(const U1& u1);
    template <typename T, typename U1> static T* create_p(const U1& u1);
    //... all the required possibilities can be generated by prepro loops
};

const int A::checkValue = 159736482; // or any random value

A::A(int certificate)
{
  assert(certificate == A::checkValue);
}

A::A(const A& a)
{}

A::~A()
{}

template <typename T>
T A::create()
{
  T t(A::checkValue);
  t.init();
  return t;
}

template <typename T>
T* A::create_p()
{
  T* t = new T(A::checkValue);
  t->init();
  return t;
}

template <typename T, typename U1>
T A::create(const U1& u1)
{
  T t(A::checkValue, u1);
  t.init();
  return t;
}

template <typename T, typename U1>
T* A::create_p(const U1& u1)
{
  T* t = new T(A::checkValue, u1);
  t->init();
  return t;
}

class B : public A
{
  public:
    B(int certificate);
    B(const B& b);
    virtual ~B();
    virtual void init();
};

B::B(int certificate) :
  A(certificate)
{}

B::B(const B& b) :
  A(b)
{}

B::~B()
{}

void B::init()
{
  std::cout << "call B::init()" << std::endl;
}

class C : public A
{
  public:
    C(int certificate, double x);
    C(const C& c);
    virtual ~C();
    virtual void init();
  private:
    double x_;
};

C::C(int certificate, double x) :
  A(certificate)
  x_(x)
{}

C::C(const C& c) :
  A(c)
  x_(c.x_)
{}

C::~C()
{}

void C::init()
{
  std::cout << "call C::init()" << std::endl;
}

Then, the user of the class can't construct an instance without giving the certificate, but the certificate can only be produced by the creation functions:那么,类的用户不给证书就不能构造实例,而证书只能由创建函数产生:

B b = create<B>(); // B::init is called
C c = create<C,double>(3.1415926535); // C::init is called

Moreover, the user can't create new classes inheriting from AB or C without implementing the certificate transmission in the constructor.而且,如果没有在构造函数中实现证书传输,用户就不能创建继承自 AB 或 C 的新类。 Then, the base class A has the warranty that init will be called after construction.然后,基类 A 保证在构造后将调用 init。

I can offer a work around / "companion" to your abstract base class using MACROS rather than templates, or staying purely within the "natural" constraints of the language.我可以使用宏而不是模板为您的抽象基类提供解决方案/“伴侣”,或者完全保持在语言的“自然”约束范围内。

Create a base class with an init function eg:使用 init 函数创建一个基类,例如:

class BaseClass
{
public:
    BaseClass(){}
    virtual ~BaseClass(){}
    virtual void virtualInit( const int i=0 )=0;
};

Then, add a macro for a constructor.然后,为构造函数添加一个宏。 Note there is no reason to not add multiple constructor definitions here, or have multiple macros to choose from.请注意,没有理由不在此处添加多个构造函数定义,或者有多个宏可供选择。

#define BASECLASS_INT_CONSTRUCTOR( clazz ) \
    clazz( const int i ) \
    { \
        virtualInit( i ); \
    } 

Finally, add the macro to your derivation:最后,将宏添加到您的派生中:

class DervivedClass : public BaseClass
{
public:
    DervivedClass();
    BASECLASS_INT_CONSTRUCTOR( DervivedClass )
    virtual ~DervivedClass();

    void virtualInit( const int i=0 )
    {
        x_=i;
    }

    int x_;
};

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 来自Base Ctor的Pure Virtual Function调用 - Pure Virtual Function call from Base Ctor 在从基类构造函数创建的单独线程中调用纯虚函数 - Calling a pure virtual function in a separate thread that is created from base-class constructor 如果在两个以上的派生类中定义了纯虚函数,该如何从基类调用纯虚函数 - how to call pure virtual function from base class if that function is defined in more than 2 derived class 从父类调用纯虚函数 - Call pure virtual function from parent class (为什么)在纯虚拟派生类中是否需要虚拟基类构造函数调用? - (Why) Is virtual base class constructor call required in pure virtual derived class? 从抽象基类成员函数调用纯虚函数? - Calling Pure Virtual Function From Abstract Base Class Member Function? 来自基本 ctor 的纯虚拟“非常 const”function 调用 - Pure virtual “very const” function call from base ctor 为什么从构造函数对纯虚函数的虚拟调用是UB,标准允许调用非纯虚函数? - Why a virtual call to a pure virtual function from a constructor is UB and a call to a non-pure virtual function is allowed by the Standard? 从基类实例获取指向(纯)虚函数的指针 - Getting a pointer to a (pure) virtual function from a base class instance 从未定义基类的纯虚函数中调用它? - Calling a pure virtual function from base class that doesn't define it?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM