tags:

views:

214

answers:

6

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?

+2  A: 

Hi,

You're doing something rather weird here.

The following would work and is entirely safe:

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

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

In this hierarchy, when CDerived is constructed, first CBase performs its SomeChecks() and then CDerived is doing its own OtherChecks(). That's how it should be.

The fact that you made SomeChecks() virtual shows an intention to allow SomeChecks to be fully overridden in derived classes, while those checks still have to be executed in constructor. This is usually an indication of broken architecture; in fact, you are trying to put some knowledge of the derived class in the parent, which is generally wrong.

blinnov.com
My question maybe wasn't complete. In my case the Constructor reads a XML file and checks the elements. A derived class can add elements to the XML where the base doesn't know anything about. So when CBase() walks through the elements, he forwards the elements to CDerived::SomeOtherChecks() to handle those elements.I agree with the "broken architecture", such thing should be avoided.Probably I'm turning this in to a bigger problem than required... :-)
To1ne
Why not turn this around. Have the derived constructor check the elements, and forward any it doesn't know about to the base class.
KeithB
+1  A: 

There's no clean solution to this. You can't call CDerived functions safely until the CDerived ctor body has been entered. At that point the CBase ctor must have returned.

One workaround might be the following:

protected: CBase(boost::function<void(*)(CBase*)> SomeChecks) {
    // Base checks
    SomeChecks(this); // Checks provieded by derived ctor but running on CBase member.
}
MSalters
+3  A: 

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.

sbi
A: 

One should design one's class hierarchy in such a way that only a base class is responsible for checking the base constraints. This can be a problem when for instance the child class has to invent some constructor arguments for the parent class' constructor.

The other problem, calling another constructor with the same argument signature, can be solved using the 'tagged constructor' trick: create a template constructor.

struct C {
   enum eConstructor { cCheck, cNoCheck };

   template< eConstructor constr = cCheck > C( int i );

   int positive_value;
};

template<> C::C<C:: cCheck >( int i ) : positive_value( std::max( 0, i ) ) { }
template<> C::C<C::cNoCheck>( int i ) : positive_value( i ) { }


struct CFive : public C {
   CFive(): C<C::cNoCheck>(5) {}
};
xtofl
A: 

Maybe this works for you:

class CBase {
   public:
      CBase() {
         /* Do some checks */
         SomeChecks();
         /* Do some more checks */
      }
      virtual ~CBase(){} /*don't forget about this*/
      virtual void SomeChecks() {}
};


class CDerived : public CBase {
   public:
      void SomeChecks() { //without virtual
           /* Do some other checks */
           CBase::SomeChecks(); //if you want checks from CBase
      }
      CDerived() : CBase() {}
};

CBase* fromBase = new CBase(); //checking with CBase::SomeChecks()
CBase* fromDerived = new CDerived(); //checking with CDerived::SomeChecks
CDerived* alsoDerived = new CDerived(); //checking with CDerived::SomeChecks
grayasm
No, this won't work. If a function is `virtual` in a base class, all overrides in derived classes are `virtual`, too, whether they are marked as such or not. And you cannot call virtual functions from constructors or destructors. (Well, you can, but the result is surprising for most who try it.)
sbi
The fact that I learn only from my faults is obvious, because I didn’t run into this c++ trap. I remember about it in a book, but never bother to memorize it. Good to know.
grayasm
@vmihai: Have you looked at Scott Meyers' "Effective C++"? A nice set of rules (IIRC, it's 53 in the latest edition), well presented, thoroughly explained, easy to remember. I bet the one not to call virtual functions from constructors and destructors is one of them.
sbi
+2  A: 

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;
}
Martin York
As stated, calling virtual methods in constructors does not lead to desired behavior. I think the pimpl solution is most reasonable. The topic is covered good in Scott Meyers Effective C++ book.
count0
Actually, this isn't a pimpl. At least, the thing called so isn't. See here: http://en.wikipedia.org/wiki/Opaque_pointer#C.2B.2B
sbi
Actually it is. :-) Read your wiki page. But a good book on Design Patterns like GOF would probably be better.
Martin York
Which are good books about Design Patterns? GOF?
To1ne
GOF: => Gang of Four (The original seminal work on design patterns). A quick google on "Design Patterns GOF" gives me: http://www.amazon.com/Design-Patterns-Elements-Reusable-Object-Oriented/dp/0201633612
Martin York
@Martin: This was a misunderstanding. While your example indeed implements a pimpl, I feel that what you named `PIMPL` isn't the actual pimpl - that you name 'm_data'. However, looking at it now I can see that you probably named it that way because it implements the pattern. (Oh, and I guess it's at least ten years ago that I read th GoF book.)
sbi