2010.01.09 - C++ and 'final' virtual methods

as we all know there is no 'final' key word in C++, as is in for example Java or similar 'sealed' keyword in C#. it is also not planned to be introduced in C++0x. until just a few days ago i didn't find it an interesting topic myself… until i came across such a need.

situation

what i had were 2 set of classes: BaseA (with pure-virtual methods), BaseB (also with pure-virtuals) and many classes derived from one of these two. in fact, there were 2 disjoint class hierarchies, except for one thing – both BaseA and BaseB had identical public interface, though private, pure-virtual methods differed. the simplified version could look like this:

class BaseA
{
public:
  void process(Sth o)
  {
    // [some other code here]
    findQuestion(o, 42);
    // [some other code here]
  }
 
private:
  virtual void findQuestion(Sth o, int answer) = 0;
};
class BaseB
{
public:
  void process(Sth o)
  {
    if( check(o) )
      doIt(o);
    // [some other code here]
  }
 
private:
  virtual bool check(Sth o) = 0;
  virtual void doIt(Sth o) = 0;
};

what happens in process() in both classes is typical template method pattern. since public APIs are identical, why not make it common? these classes were to be used together either way! so what do we do? we add base class to these two – let us call it CommonBase.

class CommonBase
{
public:
  virtual ~CommonBase(void) { }
  virtual void process(Sth o) = 0;
};

now let us derive BaseA and BaseB from our new class!

class BaseA: public CommonBase
{
public:
  virtual void process(Sth o) // final
  {
    // [some other code here]
    findQuestion(o, 42);
    // [some other code here]
  }
 
private:
  virtual void findQuestion(Sth o, int answer) = 0;
};
class BaseB: public CommonBase
{
public:
  virtual void process(Sth o) // final
  {
    if( check(o) )
      doIt(o);
    // [some other code here]
  }
 
private:
  virtual bool check(Sth o) = 0;
  virtual void doIt(Sth o) = 0;
};

the problem

and here the problem arises – what if user accidentally overwrites process() call? she can do this, since its virtual call now. if i'd happen, compiler would not warn her, and application would be damaged, since code would not behave as expected in such derived classes – problems would not be so obvious in bigger application, where side effects of code in process() were minimal…

what i wanted to do was use 'final'-like mechanism, which C++ does not provide and will not provide in reasonable future. and so i start digging… i came across number of forums (ex.: _1_ _2_ _3_ and so on…) sometimes find comments like “just insert the comment this method cannot be inherited from and it will be just fine 90% of the cases”_4_. my answer is: NO – it won't be fine, since if there is a simple way to make a mistake, it will be made – it's just a matter of time…

the solution

after a while i came to a conclusion – it can be made, though it requires some extra effort. my idea is to use static polymorphism to achieve this. trick is to break inheritance where process() is implemented. and so we make template-based derived class of CommonBase that takes strategy class as a parameter. this strategy implements process() as a non-virtual call, with proper call order and other stuff we need and defines pure-virtual methods – all as in original code. and so we have something like this:

class CommonBase
{
public:
  virtual ~CommonBase(void) { }
  virtual void process(Sth o) = 0;
};
template<typename TImpl>
class CommonBaseImpl: public CommonBase
{
public:
  virtual void process(Sth o)
  {
    impl_.process(o);
  }
 
private:
  TImpl impl_;
};
class StrategyA
{
public:
  void process(Sth o)  // notice - not a virtual call!
  {
    // [some other code here]
    findQuestion(o, 42);
    // [some other code here]
  }
 
private:
  virtual void findQuestion(Sth o, int answer) = 0;
};
class StrategyB
{
public:
  void process(Sth o)   // notice - not a virtual call!
  {
    if( check(o) )
      doIt(o);
    // [some other code here]
  }
 
private:
  virtual bool check(Sth o) = 0;
  virtual void doIt(Sth o) = 0;
};

now we're almost done. all we need to do is to implement our strategies the way we want and create instances. so it's go!

class StrategyA_1: public StrategyA
{
private:
  virtual void findQuestion(Sth o, int answer)
  {
    // ...
  }
};
class StrategyB_1: public StrategyB
{
private:
  virtual bool check(Sth o)
  {
    // ...
  }
  virtual void doIt(Sth o)
  {
  }
};

now we can create an instance and run it:

  std::auto_ptr<CommonBase> base( new CommonBaseImpl<StrategyA_1> );
  base->process();

and that's all - we have running code, where it is not possible for user to accidentally overwrite relevant code, she should not be possible to.

download

to see how this works in real-life you can download 'final-like' example code and run it yourself. package contains 4 different implementations of 2 different strategies, so you're free to do experiments or used it as you want.

example is licensed as beerware, if you plan to used it for more than just exercises. ;)

c-tor parameter(s)

there are few things that can be improved in above example. first of all is constructor – in most real-life cases c-tors do take parameter(s), so default c-tor is not enough. i skipped this step in above example just to stick to the point and not add additional noise to the basic idea. when you want to be able to pass any parameter(s) in generic way you should modify CommonBaseImpl like this:

template<typename TImpl, typename TParam=int>
class CommonBaseImpl: public CommonBase
{
public:
  CommonBaseImpl(const TParam &p):
    impl_(p)
  {
  }
  CommonBaseImpl(void)
  {
  }
 
  virtual void process(Sth o)
  {
    impl_.process(o);
  }
 
private:
  TImpl impl_;
};

in case you need more paramters, make TParam ba a std::pair<>, boost::tuple<>, or just a dedicated structure that holds all of your parameters and so you can passes anything you want to any possible implementation you want.

minimizing virtual calls

if you've read this carefully you probably noticed that there is no real need for using virtual calls when writing StrategyX and its derived classes. you can do the same trick as with CommonBase – just make StrategyX_Y (implementation) a template parameter to StrategyX using CRTP.

i've skipped this point again for sake of clarity. adding more templates (1 – if you stick just to CRTPl; 2 – if you need c-tor parameters to be passed (see example before)). this step does complicate code even more, and efficiency difference is minimal, since virtual calls does not cost much1) – if this is not very time-critical code part, you can just stick to the virtuals – remeber: architecture and readability first, then optimizations2)!

final remarks

as you can see, one can live without 'final' keyword but it's not straight-forward nor does explicitly express programmer's intentions. why there is no such keyword in C++, i do not know. it would be helpful if there were – in a way it is in C++ spirit, since it does not introduce any overhead (this is just a compile-time check, and since compiler have to know full class declaration when deriving, it could be always checked) but does simplify life.

1)
i've measured it on my old Pentium IV to be ~2-3% during millions of executions of simple (but not inlined!) method
2)
off course time-critical parts are exception here, but these are typically estimated as ~2% of all code.
blog/2010/2010.01.09.txt · Last modified: 2013/05/17 19:08 (external edit)
Back to top
Valid CSS Driven by DokuWiki Recent changes RSS feed Valid XHTML 1.0