Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to ensure that virtual method calls get propagated all the way to the base class?

One very common mistake with class hierarchies is to specify a method in a base class as being virtual, in order for all overrides in the inheritance chain to do some work, and forgetting to propagate the call on to base implementations.

Example scenario

class Container
{
public:
  virtual void PrepareForInsertion(ObjectToInsert* pObject)
  {
    // Nothing to do here
  }
};

class SpecializedContainer : public Container
{
protected:
  virtual void PrepareForInsertion(ObjectToInsert* pObject)
  {
    // Set some property of pObject and pass on.
    Container::PrepareForInsertion(pObject);
  }
};

class MoreSpecializedContainer : public SpecializedContainer
{
protected:
  virtual void PrepareForInsertion(ObjectToInsert* pObject)
  {
    // Oops, forgot to propagate!
  }
};

My question is: is there a good way/pattern to ensure that the base implementation always gets called at the end of the call chain?

I know of two methods to do this.

Method 1

You can use a member variable as a flag, set it to the correct value in the base implementation of the virtual method, and check its value after the call. This requires to use a public non-virtual method as interface for the clients, and making the virtual method protected (which is actually a good thing to do), but it requires the use of a member variable specifically for this purpose (which needs to be mutable if the virtual method must be const).

class Container
{
public:
  void PrepareForInsertion(ObjectToInsert* pObject)
  {
    m_callChainCorrect = false;
    PrepareForInsertionImpl(pObject);
    assert(m_callChainCorrect);
  }

protected:
  virtual void PrepareForInsertionImpl(ObjectToInsert* pObject)
  {
    m_callChainCorrect = true;
  }

private:
  bool m_callChainCorrect;
};

class SpecializedContainer : public Container
{
protected:
  virtual void PrepareForInsertionImpl(ObjectToInsert* pObject)
  {
    // Do something and pass on
    Container::PrepareForInsertionImpl(pObject);
  }
};

Method 2

The other way to do it is to replace the member variable with an opaque "cookie" parameter and do the same thing:

class Container
{
public:
  void PrepareForInsertion(ObjectToInsert* pObject)
  {
    bool callChainCorrect = false;
    PrepareForInsertionImpl(pObject, &callChainCorrect);
    assert(callChainCorrect);
  }

protected:
  virtual void PrepareForInsertionImpl(ObjectToInsert* pObject, void* pCookie)
  {
    *reinrepret_cast<bool*>(pCookie) = true;
  }
};

class SpecializedContainer : public Container
{
protected:
  virtual void PrepareForInsertionImpl(ObjectToInsert* pObject, void* pCookie)
  {
    // Do something and pass on
    Container::PrepareForInsertionImpl(pObject, pCookie);
  }
};

This approach is inferior to the first one in my opinion, but it does avoid the use of a dedicated member variable.

What other possibilities are there?

like image 580
Carl Seleborg Avatar asked Aug 13 '09 17:08

Carl Seleborg


People also ask

How do you call a virtual method from base class?

When you want to call a specific base class's version of a virtual function, just qualify it with the name of the class you are after, as I did in Example 8-16: p->Base::foo(); This will call the version of foo defined for Base , and not the one defined for whatever subclass of Base p points to.

Can you call a pure virtual function in base class?

In our discussion __purecall , we saw that you can declare a pure virtual function with the = 0 syntax, and if you try to call one of these functions from the base class, you will get the dreaded R6025 – pure virtual function call error.

Can virtual methods be inherited?

Base classes can't inherit what the child has (such as a new function or variable). Virtual functions are simply functions that can be overridden by the child class if the that child class changes the implementation of the virtual function so that the base virtual function isn't called. A is the base class for B,C,D.

What is a virtual base class why it is important to make a class virtual?

Virtual base classes are used in virtual inheritance in a way of preventing multiple “instances” of a given class appearing in an inheritance hierarchy when using multiple inheritances. Need for Virtual Base Classes: Consider the situation where we have one class A .


2 Answers

You've come up with some clever ways to do this, at (as you acknowledge) the cost of bloating the class and adding code that addresses not the object's responsibilities but programmer deficiences.

The real answer is not to do this at runtime. This is a programmer error, not a runtime error.

Do it at compile time: use a language construct if the language supports it, or use a Pattern the enforces it (e.g,, Template Method), or make your compilation dependent on tests passing, and set up tests to enforce it.

Or, if failing to propagate causes the derived class to fail, let it fail, with an exception message that informs the author of the derived class that he failed to use the base class correctly.

like image 96
tpdi Avatar answered Oct 20 '22 00:10

tpdi


What you are looking for is simply the Non-Virtual Interface pattern.

It's similar to what you are doing here, but the base class implementation is guaranteed to be called because it's the only implementation that can be called. It eliminates the clutter that your examples above require. And the call through the base class is automatic, so derived versions don't need to make an explicit call.

Google "Non-Virtual Interface" for details.

Edit: After looking up "Template Method Pattern", I see that it's another name for Non-Virtual Interface. I've never heard it referred to by the name before (I'm not exactly a card-carrying member of the GoF fan club). Personally, I prefer the name Non-Virtual Interface because the name itself actually describes what the pattern is.

Edit Again: Here's the NVI way of doing this:

class Container
{
public:
  void PrepareForInsertion(ObjectToInsert* pObject)
  {
    PrepareForInsertionImpl(pObject);

    // If you put some base class implementation code here, then you get
    // the same effect you'd get if the derived class called the base class
    // implementation when it's finished.
    //
    // You can also add implementation code in this function before the call
    // to PrepareForInsertionImpl, if you want.
  }

private:
  virtual void PrepareForInsertionImpl(ObjectToInsert* pObject) = 0;
};

class SpecializedContainer : public Container
{
private:
  virtual void PrepareForInsertionImpl(ObjectToInsert* pObject)
  {
    // Do something and return to the base class implementation.
  }
};
like image 30
Dan Moulding Avatar answered Oct 19 '22 22:10

Dan Moulding