Journal Articles

CVu Journal Vol 14, #4 - Aug 2002 + Programming Topics
Browse in : All > Journals > CVu > 144 (17)
All > Topics > Programming (877)
Any of these categories - All of these categories

Note: when you create a new publication type, the articles module will automatically use the templates user-display-[publicationtype].xt and user-summary-[publicationtype].xt. If those templates do not exist when you try to preview or display a new article, you'll get this warning :-) Please place your own templates in themes/yourtheme/modules/articles . The templates will get the extension .xt there.

Title: Examining C++

Author: Administrator

Date: 03 August 2002 13:15:53 +01:00 or Sat, 03 August 2002 13:15:53 +01:00

Summary: 

Body: 

Recently I have been involved in both sides of the recruitment process, and noticed there are a couple of popular questions at an interview for a C++ position that are pretty similar wherever you go. These questions are not designed to pick out the star candidates, but rather identify someone whose C++ knowledge may not be all this is desired. Unfortunately, by exploring a little beyond these simple questions I discovered that frequently the interviewers themselves don't really understand the topics they are examining. In this short series of articles we will examine this difference between learning and understanding by looking the classic form of two interview questions and exploring some alternative solutions. We begin with the most frequently encountered question with the most frequently misapplied solution:

Do you see any errors in the following code?

#include <ostream>
using std::cout;
using std::endl;

class base {
public:
  base( int nData = 0 )
    : m_pnData1( new int( nData ) ) {}
  ~base( void ) { delete m_pnData1; }
  virtual int Data( void ) {
    return *m_pnData1; 
  }
private:
  int *m_pnData1;
};

class derived : public base {
public:
  derived( int nData = 0 )
    : base( 5 )
    , m_pnData2( new int(nData) ) { }
  ~derived( void ) { delete m_pnData2; }
  int Data( void ) { return *m_pnData2; }
private:
  int *m_pnData2;
};

int main( void ) {
  base *pBase = new derived(12);
  cout << "Data = " << pBase->Data() << endl;
  delete pBase;
  return 0;
}

The trick we are looking for is that base does not have a virtual destructor. Consequently, when pBase is deleted it does not call the destructor of derived and there will be a memory leak. The solution is therefore to fix base.

virtual ~base( void ) {
  delete m_pnData1;
}

This is as far as a 'standard' interview will take this question. However, what happens if instead we rewrite main as follows?

int main( void ) {
  derived example(12);
  cout << "Data = " << example.Data() << endl;
  return 0;
}

Now we have a simple instance of derived. Do we still have a problem? Will m_pnData1 in the base class truly be deleted, or will something even more bizarre happen?

The answer is of course that there is no problem with this code now. It will work as we intended and there is no leak. Frequently though, developers I meet are so taken with the virtual destructor mantra that they will insist the only way to fix this problem is to make all destructors virtual. Often they cannot see that the second solution will indeed run correctly. Typically candidates who have not seen the solution phrased this way before are uneasy, perhaps coming to accept the answer but with a feeling that they are being fooled and that really 'something else' will happen although they can't see what. I put this down to seeing and learning the same example over and over without any attempt being made to really teach an understanding of what is happening.

Now if they have really been paying attention a star candidate will complain at this point that although it solves the problem at hand, this code is still dangerous. derived is still dangerous to use through a pointer to base as seen in the original problem, and we have not stopped people using it in this manner. This is a good point, as a fragile solution that relies on people strictly following coding conventions is simply storing up a problem as a project evolves. One day someone new will join the team, not knowing all the conventions, and make this mistake. You then have a very hard bug to track.

However, there is one further change we can make that will allow us to keep the second solution intact. The problem is that although it is fine to delete a pointer to derived, it is dangerous to call delete on a pointer to base. To enforce correct usage, we make the destructor of base protected. Now the compiler will catch any attempt to delete a base for us, and the compiler is an unforgiving observer of coding conventions when we let it know about them!

Some of you may still be wondering if all we have done here is move the problem from base to derived. This is another good observation, and in the simple example provided the best solution is indeed to make the base destructor virtual. However, it is important to understand the second solution as it is a valid idiom and you may well meet it in others' code. More importantly, by understanding it you become aware of when to use it yourself.

So when might we want to use this protected non-virtual destructor idiom? After all, if virtual destructors are so important why aren't they forced on us by the language? Are there any well-known examples of classes without virtual destructors? A quick look into the Standard Template Library will reveal that many of these classes do not have virtual destructors. However, contrary to the advice in this article their destructors are also declared as public. Surely this can't be some mistake in the standard?! Indeed it isn't, and understanding why will lead us to a possible use of our idiom.

The classes in the STL are 'value' classes, where the classes are intended to be used as simple values, 'just like the ints'[2]. These classes need to be efficient with minimal overhead if they are to be used in this manner, otherwise we may look for hacks such as passing pointers to the values around when efficiency really matters to us. A virtual destructor brings no advantage to such a class, as they are not intended to be derived from (any more than you might derive from an int, if such a thing were possible[3]. However it does bring overhead in the form of a virtual function table, and all calls to destroy these objects now go by virtual dispatch involving an indirect call through a function pointer that also kills the opportunity for inlining. This may not sound like much, but when you have thousands of these objects with a short lifetime it quickly adds up. So by design a valueclass has a no virtual functions and a public, non-virtual destructor[2], and is not intended for use as a base class. Clearly, this does not match our intended use of base as a base class, so although this is the best-known example for non-virtual destructors it is not the answer we are looking for.

Looking the other way, typical use of a public virtual destructor is when a base class provides a polymorphic interface for a family of classes. The standard example here is the vector drawing package with the classes circle, square and triangle all deriving from an abstract interface shape. The program works with references to shapes, which it typically obtains through calls to a class factory[4][5] of some kind. The concrete objects are always owned and manipulated through these references (typically some kind of smart pointer). The base class provides the interface, and a required part of that interface is a public destructor in order to manage the lifetime of the objects. So clearly our idiom will not apply here either, as we cannot expose the public destructor.

Where does this leave us? We have a class intended for use as a base class, but without public destructors. The only place we can really use this is as an implementation detail of another class. The subclass derives privately from the base, as it is an implementation detail and nothing to do with the public interface. A good example might be an application of the Template Method pattern[6] (nothing to do with C++ template syntax, despite the name!) For example let's design a report-printing class. We know that when we print a report we will want to print a title page, the content of the report, and then the bibliography. This can be encapsulated into a (non-virtual) Print method that calls three abstract virtual functions, PrepareTitlePage, PrepareReport and PrepareBibliography. In order to implement a report now all we need to do is derive privately from the ReportTemplate class and override these three functions. The routine details of taking the result of these operations and getting them to the printer are handled by the base class Print implementation itself. Clients of our reports don't care that they are implemented in terms of a ReportTemplate class, they simply want to create an instance of our AccountsReportPrint class and run the print job.

In summary the destructor for a value class should be non-virtual and public, whereas the destructor for a base class should be either virtual and public, or more rarely non-virtual and protected. It is left as an exercise to the reader to find a use for private destructors!

Hopefully this article has shown you that preparing good interview questions is hard, and dealing with the answers can be harder! Along the way you have picked up or confirmed your ideas on the design decisions to consider when deciding how to declare the destructor for your classes, and why simply declaring everything virtual is not the only way.

The conscientious among you are already worrying about a host of other fragilities in the example classes. Have no fear! These will be addressed in the next two articles on Temporary Values and Exception Safety.



[1] The title is an affectionate nod to two classic books, Effective C++ and Exceptional C++, where I first learned most of the ideas contained in these articles, along with the value of a good book.

[2] Effective C++, Scott Meyers. Published Addison Wesley 1998? (second edition)

[3] Actually this IS possible in other languages where everything is an object, such as Java or Smalltalk, although I am not aware of this being a popular thing to do! (and may be one motivation for the 'final' keyword in Java)

[4] Factory pattern, Design Patterns by Gamma, Helm, Johnson and Vlissides. Published Addison Wesley 1995

[5] Factory pattern, Modern C++ Design Alexei Alexandrescu. Published Addison Wesley 2001

[6] Template method, Design Patterns by Gamma, Helm, Johnson and Vlissides. Published Addison Wesley 1995

Notes: 

More fields may be available via dynamicdata ..