Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Safe way to initialize a derived class

Tags:

c++

I have a base class:

class CBase {
   public:
      virtual void SomeChecks() {}
      CBase() {
         /* Do some checks */
         SomeChecks();
         /* Do some more checks */
      }
};

and a derived class:

class CDerived : public CBase {
   public:
      virtual void SomeChecks() { /* Do some other checks */ }
      CDerived() : CBase() {}
};

This construction seems to be a bit weird but in my case this is required, because CBase does some checks and CDerived can mix some checks in between them. You can see it as a way to "hook" functions in the constructor. The problem with this construction is that while constructing CDerived first a CBase is constructed and there is no awareness of CDerived (so overloaded function SomeChecks() is not called).

I could do something like this:

class CBase {
   public:
      void Init() {
         /* Do some checks */
         SomeChecks();
         /* Do some more checks */
      }
      virtual void SomeChecks() {}
      CBase(bool bDoInit=true) {
         if (bDoInit) { Init(); }
      }
};
class CDerived : public CBase {
   public:
      virtual void SomeChecks() { /* Do some other checks */ }
      CDerived() : CBase(false) { Init() }
};

This isn't really safe, because I want the constructor with the false parameter be protected, so only derived classes can call it. But then I'll have to create a second constructor (that is protected) and make it take other parameters (probably unused because is constructor is called when Init() does not have to be called).

So I'm quite stuck here.

EDIT Actually I want something like this:

class CBase {
   protected:
      void Init() { /* Implementation of Init ... */ }
      CBase() { /* Don't do the Init(), it is called by derived class */ }
   public:
      CBase() { Init(); }     // Called when an object of CBase is created
};
class CDerived : public CBase {
   public:
      CDerived() : CBase() { Init(); }
};

It seems to me it is impossible to have 2 constructors with the same arguments being protected and public?

like image 600
To1ne Avatar asked Feb 28 '23 07:02

To1ne


2 Answers

Calling virtual methods in the constructor/destructor is not allowed.
The though processes behind this is that virtual methods are calling the most derived version of a method and if the constructor has not finished then the most derived data has not been correctly initialized and therefore doing so potentially provides an opertunity for use of an invalid object.

What you are looking for is the PIMPL design pattern:

class CBase { ... };
class CDerived: public CBase { ... }

template<typename T>
class PIMPL
{
    public:
        PIMPL()
            :m_data(new T)
        {
           // Constructor finished now do checks.
           m_data->SomeChecks();
        }
        // Add appropriate copy/assignment/delete as required.
    private:
        // Use appropriate smart pointer.
        std::auto_ptr<T>    m_data;
};
int main()
{
    PIMPL<CDerived>    data;
}
like image 172
Martin York Avatar answered Mar 06 '23 21:03

Martin York


What you want is called two-phase construction. C++ doesn't offer this as a syntactcical construct, so you have to do it on your own.

A common way to do this is to use the Programmer's All-Purpose Ointment (add another layer of indirection): You wrap your classes in some other class. That class's constructor first calls your class' constructor and then the additional initialization function. Of course, this messes up your design a bit.

like image 23
sbi Avatar answered Mar 06 '23 21:03

sbi