GotW #69

Home Blog Talks Books & Articles Training & Consulting

On the
blog
RSS feed November 4: Other Concurrency Sessions at PDC
November 3
: PDC'09: Tutorial & Panel
October 26: Hoare on Testing
October 23
: Deprecating export Considered for ISO C++0x

This is the original GotW problem and solution substantially as posted to Usenet. See the book Exceptional C++ Style (Addison-Wesley, 2004) for the most current solution to this GotW issue. The solutions in the book have been revised and expanded since their initial appearance in GotW. The book versions also incorporate corrections, new material, and conformance to the final ANSI/ISO C++ standard (1998) and its Technical Corrigendum (2003).

Enforcing Rules for Derived Classes 
Difficulty: 5 / 10

Too many times, just being at the top of the (inheritance) world doesn't mean that you can save programmers of derived classes from simple mistakes. But sometimes you can! This issue is about safe design of base classes, so that derived class writers have a more difficult time going wrong.

Problem

JG Question

1. When are the following functions implicitly declared and implicitly defined for a class, and with what semantics? Be specific, and describe the circumstances under which the implicitly defined versions cause the program to be illegal (not well-formed).

a) default constructor

b) copy constructor

c) copy assignment operator

d) destructor

2. What functions are implicitly declared and implicitly defined for the following class X? With what signatures?

class X
{
  auto_ptr<int> i_;
};

Guru Question

3. Say that you have a base class which requires that all derived classes not use one or more of the implicitly declared and defined functions. For example:

class Count
{
public:
  // The Author of Count hereby documents that derived
  // classes shall inherit virtually, and that all their
  // constructors shall call Count's special-purpose
  // constructor only.

  Count( /* special parameters */ );
  Count& operator=( const Count& ); // does the usual
  virtual ~Count();                 // does the usual
};

Unfortunately, programmers are human too, and they sometimes forget that they should write two of the functions explicitly.

class BadDerived : private virtual Count
{
  int i_;

  // default constructor:
  //   should call special ctor, but does it?
  // copy constructor:
  //  should call special ctor, but does it?
  // copy assignment: ok?
  // destructor: ok?
};

In the context of this example, is there a way for the author of Count to force derived classes to be coded correctly -- that is, to break at compile time (preferable) or run time (at minimum) if not coded correctly?

More generally, Is there any way that the author of a base class can force authors of derived classes to explicitly write each of these four basic operations? If so, how? If not, why not?

Solution

Implicitly Generated Functions (or, What the Compiler Does For/To You)

In C++, four class member functions can be implicitly generated by the compiler: The default constructor, the copy constructor, the copy assignment operator, and the destructor.

The reason for this is a combination of convenience and backward compatibility with C. Recall that C-style structs are just classes consisting of only public data members; in particular, they don't have any (explicitly defined) member functions, and yet you do have to be able to create, copy, and destroy them. To make this happen, the C++ language automatically generates the appropriate functions (or some appropriate subset thereof) to do the appropriate things, if you don't define appropriate operations yourself.

This issue of GotW is about what all of those "appropriate" words mean.

1. When are the following functions implicitly declared and implicitly defined for a class, and with what semantics? Be specific, and describe the circumstances under which the implicitly defined versions cause the program to be illegal (not well-formed).

In short, an implicitly declared function is only implicitly defined when you actually try to call it. For example, an implicitly declared default constructor is only implicitly defined when you try to create an object using no constructor parameters.

Why is it useful to distinguish between when the function is implicitly declared and when it's implicitly defined? Because it's possible that the function might never be called, and if it's never called then the program is still legal even if the function's implicit definition would have been illegal.

For convenience, throughout this article unless otherwise noted "member" means "nonstatic class data member." I'll also say "implicitly generated" as a catchall for "implicitly declared and defined."

Exception Specifications of Implicitly Declared Functions

In all four of the cases where a function can be implicitly declared, the compiler will make its exception specification just loose enough to allow all exceptions that could be allowed by the functions the implicit definition would call. For example, given:

// Example 1(a): Illustrating the exception
// specifications of implicitly declared functions
//
class C // ...
{
  // ...
};

Because there are no constructors explicitly declared, the implicitly generated default constructor has the semantics of invoking all base and member default constructors. Therefore the exception specification of C's implicitly generated default constructor must allow any exception that any base or member default constructor might emit. If -any- base class or member of C has a default constructor with no exception specification, the implicitly declared function can throw anything:

// public:
inline C::C(); // can throw anything

If every base class or member of C has a default constructor with an explicit exception specification, the implicitly declared function can throw any of the types mentioned in those exception specifications:

// public:
inline C::C() throw(
  // anything that a C base or member default
  // constructor might throw; i.e., the union of
  // all types mentioned in C base or member
  // default constructor exception specifications
                   );

It turns out that there's a potential trap lurking here. Consider: What if one of the implicitly generated functions overrides an inherited virtual function? This can't happen for constructors (because constructors are never virtual), but it can happen for the copy assignment operator (if you try hard, and make a base version that matches the implictly generated derived version's signature[1]), and it can happen pretty easily for the destructor:

// Example 1(b): Danger, Will Robinson!
//
class Derived;

class Base
{
public:
  // Somewhat contrived, and almost certainly deplorable,
  // but technically it is possible to use this trick to
  // declare a Base assignment operator that takes a
  // Derived argument; be sure to read [3] before even
  // thinking about trying anything like this:
  //
  virtual Base& /* or Derived& */
  operator=( const Derived& ) throw( B1 );

  virtual ~Base() throw( B2 );
};

class Member
{
public:
  Member& operator=( const Member& ) throw( M1 );
  ~Member() throw( M2 );
};

class Derived : public Base
{
  Member m_;

  // implicitly declares four functions:
  //   Derived::Derived();                 // ok
  //   Derived::Derived( const Derived& ); // ok
  //   Derived& Derived::operator=( const Derived& )
  //            throw( B1, M1 ); // error, ill-formed
  //   Derived::~Derived()
  //            throw( B2, M2 ); // error, ill-formed
};

What's the problem? The two functions are ill-formed because whenever you override any inherited virtual function, your derived function's exception specification must be at least as restrictive as the version in the base class. That only makes sense, after all: If it weren't that way, it would mean that code that calls a function through a pointer to the base class could get an exception that the base class version promised not to emit. For instance, if the context of Example 1(b) were allowed, consider the code:

Base* p = new Derived;

// Ouch -- this could throw B2 or M2, even though
// Base::~Base() promised to throw at most B2:
delete p;

Guideline: This is Yet Another Good Reason why every destructor should have either an exception specification of "throw()" or none at all. Besides, destructors should never throw anyway, and should always be written as though they had an exception specification of "throw()" even if that specification isn't written explicitly.[2]

Guideline: This is also Yet Another Good Reason to be careful about virtual assignment operators. See [3] for more about the hazards of virtual assignment and how to avoid them.

Now let's consider the four implicitly generated functions one at a time:

Implicit Default Constructor

a) default constructor

A default constructor is implicitly declared if you don't declare any constructor of your own. An implicitly declared default constructor is public and inline.

An implicitly declared default constructor is only implicitly defined when you actually try to call it, has the same effect as if you'd written an empty default constructor yourself, and can throw anything that a base or member default constructor could throw. It is illegal if that empty default constructor would also have been illegal had you written it yourself (for example, it would be illegal if some base or member doesn't have a default constructor).

Implicit Copy Constructor

b) copy constructor

A copy constructor is implicitly declared if you don't declare one yourself. An implicitly declared default constructor is public and inline, and will take its parameter by reference to const if possible (it's possible if and only if every base and member has a copy constructor that takes its parameter by reference to const or const volatile too), and by reference to non-const otherwise.

Yes indeed, just like most C++ programmers, the standard itself pretty much ignores the volatile keyword a lot of the time. Although the compiler will take pains to tack "const" onto the parameter of an implicitly declared copy constructor (and copy assignment operator) whenever possible, frankly -- to use the immortal words of Clark Gable in Gone with the Wind -- it doesn't give a hoot about tacking on "volatile." Oh well, that's life.

An implicitly declared copy constructor is only implicitly defined when you actually try to call it to copy an object of the given type, performs a memberwise copy of its base and member subobjects, and can throw anything that a base or member copy constructor could throw. It is illegal if any base or member has an inaccessible or ambiguous copy constructor.

Implicit Copy Assignment Operator

c) copy assignment operator

A copy assignment operator is implicitly declared if you don't declare one yourself. An implicitly declared default constructor is public and inline, returns a reference to non-const that refers to the assigned-to object, and will take its parameter by reference to const if possible (it's possible if and only if every base and member has a copy assignment operator that takes its parameter by reference to const too), and by reference to non-const otherwise. As already noted above, volatile can go hang.

An implicitly declared copy assignment operator is only implicitly defined when you actually try to call it to assign an object of the given type, performs a memberwise assignment of its base and member subobjects (including possibly multiple assignments of virtual base subobjects), and can throw anything that a base or member copy constructor could throw. It is illegal if any base or member is const, is a reference, or has an inaccessible or ambiguous copy assignment operator.

Implicit Destructor

d) destructor

A destructor is implicitly declared if you don't declare any destructor of your own. An implicitly declared destructor is public and inline.

An implicitly declared destructor is only implicitly defined when you actually try to call it, has the same effect as if you'd written an empty destructor yourself, and can throw anything that a base or member destructor could throw. It is illegal if any base or member has an inaccessible destructor, or if any base destructor is virtual and not all base and member destructors have identical exception specifications (see Exception Specifications of Implicitly Declared Functions, above).

An auto_ptr Member

2. What functions are implicitly declared and implicitly defined for the following class X? With what signatures?

// Example 2
//
class X
{
  auto_ptr<int> i_;
};

The following functions are implicitly declared as public members. Each is implicitly defined, with the indicated effects, when you write code that tries to use it.

inline X::X() throw()
// : i_()
{
}

inline X::X( X& other ) throw()
  : i_( other.i_ )
{
}

inline X& X::operator=( X&) throw()
{
  i_ = other.i_;
  return *this;
}

inline X::~X() throw()
{
}

The copy constructor and copy assignment operators take references to non-const because they can -- that's what auto_ptr's versions do. Similarly, all of the above functions have "throws-nothing" specifications because they can -- no related auto_ptr operation throws, and indeed no auto_ptr operation at all can throw.

Note that the copy constructor and copy assignment operator transfer ownership. That may not be what the author of X necessarily wants, and so often X should provide its own versions of these functions. (For more details about this and related topics, see also GotW #62.[4])

Renegade Children and Other Family Problems

3. Say that you have a base class which requires that all derived classes not use one or more of the implicitly declared and defined functions. For example:

// Example 3
//
class Count
{
public:
  // The Author of Count hereby documents that derived
  // classes shall inherit virtually, and that all their
  // constructors shall call Count's special-purpose
  // constructor only.

  Count( /* special parameters */ );
  Count& operator=( const Count& ); // does the usual
  virtual ~Count();                 // does the usual

Here we have a class that wants its derived child classes to play nice and call Count's special constructor, perhaps so that Count can keep track of the number of objects of derived types created in the system. That's a good reason to require virtual inheritance, in order to avoid double-counting if some derived class happens to inherit multiply in such a way that Count is a base class more than once.[5]

Interestingly, did you notice that Count may have a design error already? It has an implicitly generated copy constructor, which probably isn't what is wanted to keep track of a correct count. To disable that, simply declare it private without a definition:

private:
  // undefined, no copy construction
  Count( const Count& );
};

So Count wants its derived child classes to behave. But kids don't always play nice, do they? Indeed, we don't have to look far to find an example of badly behaved problem child:

Unfortunately, programmers are human too, and they sometimes forget that they should write two of the functions explicitly.

class BadDerived : private virtual Count
{
  int i_;

  // default constructor:
  //   should call special ctor, but does it?

In short, no, the default constructor not only doesn't call the special constructor, but there's an even more fundamental concern: Is there even a BadDerived default constructor at all? The answer, which probably isn't reassuring, is: Sort of. There is an implicitly- declared default constructor (okay), but if you ever try to call it the program becomes ill-formed (oops).

Let's see why this is so. First, BadDerived doesn't define any of its own constructors, so a default constructor will be implicitly declared. That's cool. But, the minute you try to use that constructor (i.e., the minute you try to create a BadDerived object, which you might think is kind of an important thing to be able to do, and you'd be right), that default constructor gets implicitly defined -- or at least it should be, but because that implicit definition is supposed to call a base default constructor that doesn't exist, the program is ill-formed. Bottom line, any program that tries to create a BadDerived object is not a conforming C++ program, and for that reason BadDerived is properly viewed as delinquent.

So is there a default constructor? Sort of. It's declared, but you can't call it, which makes it considerably less useful. When kids go renegade like this, it's just not a happy family.


  // copy constructor:
  //  should call special ctor, but does it?

For similar reasons, the implicitly generated copy constructor will be declared but, when defined, won't call the special Count constructor. With the Count class as originally shown, this copy constructor will simply call Count's implicitly generated copy constructor.

If we decide to suppress Count's implicitly generated copy constructor, as indicated earlier, then this BadDerived would have a copy constructor implicitly declared, but since it can't be implicitly defined (because Count's wouldn't be accessible) any attempt to use it would make the program not valid C++.

Fortunately, now the news starts getting a little better:


  // copy assignment: ok?
  // destructor: ok?
};

Yes, the implicitly generated copy assignment operator and destructor will both do the right thing, namely invoke (and, in the destructor's case, override) the base class versions. So the good news is that at least something worked right.

Still, all is not happy in this class family. Every household must have some minimum of order, after all. Can we not find a way to give the parents better tools to keep the peace?

Enforcing Rules For Derived Classes

In the context of this example, is there a way for the author of Count to force derived classes to be coded correctly -- that is, to break at compile time (preferable) or run time (at minimum) if not coded correctly?

The idea is not to suppress the implicit declaration (we can't), but to make the implicit definition not well-formed, so that the compiler should emit an understandable error about it.

More generally, Is there any way that the author of a base class can force authors of derived classes to explicitly write each of these four basic operations? If so, how? If not, why not?

Well, as we went through the review of what happens to implicitly declare and define the four basic operations, we kept coming across the words "inaccessible" and "ambiguous." It turns out that adding ambiguous overloads, even with different access specifies, doesn't help much. It's hard to do much better than simply make the base class functions selectively inaccessible, by declaring them private (whether they're actually defined is optional) -- and this approach works for all of the functions but one.

// Example 4: Try to force derived classes to not use
// their implicitly generated functions, by making
// Base functions inaccessible.
//
class Base
{
public:
  virtual ~Base();

  // ... other special-purpose named functions that
  // derived classes are supposed to use...

private:
  Base( const Base& ); // undefined
  Base& operator=( const Base& ); // undefined
};

This Base has no default constructor (because a user-defined constructor has been declared, if not defined), and it has a hidden copy constructor and copy assignment operator. There's no way we could hide the destructor (which must always be accessible to derived classes after all), so it might as well be public.

The idea is that, even if we do want to support a form of a given operation (for example, copy assignment), if we can't do it with the usual function then we make the usual function inaccessible and provide a named, or otherwise distinguishable, function to do the work the way we want it done.

Where does this get us? Let's see:

class Derived : private Base
{
int i_;

  // default constructor:
  //   declared, but definition ill-formed
  //   (there is no Base default constructor)

  // copy constructor:
  //   declared, but definition ill-formed
  //   (Base's copy constructor is inaccessible)

  // copy assignment:
  //   declared, but definition ill-formed
  //   (Base's copy assignment is inaccessible)

  // destructor: well-formed, will compile
};

Not bad... we got three compile-time errors out of a possible four, and it turns out that's pretty much the best we can do, or indeed need to do.

This simple solution can't handle the destructor case, but that's okay because destructors are less amenable to special-purpose replacement anyway; base versions must always be called, no two ways about it, and after all there can only be one destructor. The difficult part is usually getting any unusual constructors to be correctly called so that the base class is correctly initialized; the base class can then normally save the information it needs to do the right thing in the destructor.

There, that wasn't bad. Simple solutions are usually best. In this case there were some more complex alternatives; let's consider them briefly to reassure ourselves that none of them could do better for the destructor case, or any other case for that matter:

Alternative #1: Make base class functions ambiguous. This isn't any better, still doesn't break the implicitly generated destructor, and it's more work.

Alternative #2: Provide base versions that blow up, for example, by throwing a std::logic_error exception. This also doesn't break the implicitly generated destructor (without breaking all possible destructors), and it turns a compile-time error into a run-time error, which is not as good.

Guideline: Prefer compile-time errors to run-time errors.

Alternative #3: Provide base versions that are pure virtual. This is useless: It doesn't apply to constructors (either default or copy); it won't help with copy assignment, because the derived versions have different signatures; and it won't help with the destructor because the implicitly generated version will satisfy the requirement to define a destructor.

Alternative #4: Use a virtual base class without a default constructor, which forces each most-derived class to explicitly call the virtual base's constructor. This approach has potential for the two constructors, and has the additional advantage of working even for classes that aren't immediately derived from Base, so this is really the only alternative that could be used in conjunction with the above solution. Derived classes' implicitly generated copy assignment operators and destructors will still be valid, though.

 

Notes

1. Thanks to Joerg Barfurth for pointing out this rare but possible case.

2. H. Sutter. Exceptional C++, "Item 16 (incl. Destructors That Throw and Why They're Evil)" (Addison-Wesley, 2000).

3. S. Meyers. More Effective C++, "Item 33: Make Non-Leaf Classes Abstract"(Addison-Wesley, 1996).

4. Available online at http://ww.gotw.ca/gotw/062.htm.

5. This example is adapted from one by Marco Dalla Gasperina in his article "Counting Objects and Virtual Inheritance" (unpublished). His code didn't include the design errors I talk about next. That article was about something else, not about enforcing rules for derived classes, but his example made me think, "hmm, how could I find a way to prevent authors of derived classes from forgetting to use the special constructor when they use that Counter base class?" Thanks again for the inspiration, Marco!

Copyright © 2009 Herb Sutter