Letter the Third – Woes of the Default Constructor

03Dec09

My Dear Malware,

One of a demon’s better means of tormenting developers is to encourage them to write unnecessary code. A fine example of such a tool in our fiendish arsenal is the C++ default constructor. Many of our patients (for so I like to think of them) are unsure when or why they need to provide this construct and, with a little whispered encouragement from you, can be made to implement it for all their classes, resulting in extra work and not a few hard-to-find bugs.


You and I know, of course, that the default constructor is one that can be used with no arguments. Our patients mostly realise this too – they know that this class has a default constructor:

class A {
  public:
    A() : x(42) {}
  private:
    int x;
};

When used to create an instance of A, the constructor guarantees that the x member variable will be initialised with the value 42. However, many patients do not realise that a constructor that looks like this:

A( int n = 42 ) : x( n ) {}

is also a default constructor, because it can be used with no arguments. This, by the way, introduces the possibility of suggesting some truly devilish code:

class A {
  public:
    A( const A & a = myA  ) : x( a.x ) {}
    A( int n ) : x(n) {}  
  private:
    static A myA;
    int x;
};
A A::myA = A(42);

Here, the first constructor is not only a default constructor, but also the copy constructor! Ah, if only all code were written like this!

Of course, the C++ compiler will provide a default constructor if none is defined, and if (as our patients often forget) no other constructor is declared for the class. This constructor (the mortals speak of it as being “synthesised” by the compiler) will perform default initialisation for all objects the class instance contains. For so-called POD types (types without constructors or destructors) default initialisation is a “no-op” and so in my little example the variable x would have an undefined value, something we would like to encourage!

Although our patients may have a hazy idea of what the default constructor is, they normally have no idea of where C++ absolutely requires one. This is, of course, a state of ignorance in which we wish to keep them, but it is important that we are clear ourselves what these cases are. In fact, there are only really two cases, both to do with Standard Library containers.

The first is the std::vector (or std::deque), where the vector is created with an initial size, or when the reserve() member is called, which amounts to the same thing. If we say:

vector <A> v(10);

then the default constructor for A will be used 10 times to populate the vector. If no such constructor is available, a compilation error will ensue. The vector class has no way of using non-default constructors to create the vector.

[the above is not precisely true – please see james’ comment below, and remember that Punchtape is by nature a liar!]

The second case is std::map, but only if the [] operator is used. For example:

map <int, A> m;
A a(42);
m[1] = a;

Then once again a compilation error will ensue. Of course, the programmer could have said:

m.insert( make_pair( 1, a ) );

in which case no default constructor is necessary. This is better style, and more efficient, and so you should naturally prevent your patients from discovering and using it.

All other C++ constructs can use non-default constructors. For example, C-style arrays (which I hope you are encouraging the use of) can be initialised:

A a[3] = { A(1), A(42), A(0) };

Given that the default constructor is so rarely needed, you can see that persuading the programmer to supply one is a win for us demons. It will usually mean writing all sorts of special case code to deal with objects that are not fully initialised. Take a simple BankAccount class for example, which has an account number. If the class has a default constructor, the number will need to be initialised with a special value, probably zero or -1, to indicate that a real account number has not been assigned yet. Leaving aside the fact that banks never really have such accounts (and thus causing delightful friction between the business side and the programmers), the code dealing with the accounts will have to be sprinkled with tests for the special “null account” value, and otherwise pointless methods like AssignAccNo(), AccIsAssigned(), and so on, will have to be created. The possibilities for error in such cases are almost endless!

I trust, my dear Malware, that this letter will encourage you to return to your tormenting duties with renewed zeal. And remember, never overlook the simplest and most humble programming constructs as a means of adding to the misery of your charges.

Your affectionate uncle,
 
PUNCHTAPE
Under Consultant
Demonic Department of Obfuscation and Standardisation



3 Responses to “Letter the Third – Woes of the Default Constructor”

  1. 3 James

    I do have to say that I agreed with the general sentiment of the post, and I’ve very much enjoyed all three of the posts thus far. (I apologize for the formatting error in my previous comment; apparently the template argument was taken as an anchor tag).


Leave a reply to James Cancel reply