[Exceptional C++ Style] Item 19 - Enforcing Rules for Derived Classes.

Paul Grenyer paul at paulgrenyer.co.uk
Fri Jan 7 04:44:58 EST 2005


Posted on behalf of David Sullivan:

Implicitly Generated Functions
The four generated functions are-:
  Default constructor
  Copy constructor
  Copy assignment operator
  The destructor

These implicitly declared functions are only defined when a call is made to
it.
An illegal implicitly defined function which is not called is never defined
and
so the program is still legal.

Exception Specifications of IDF's
For all four cases above, the compiler will make it's exception
specification loose
enough to permit all exceptions that by the functions.

class C // base classes....
{
  // body of class
};

No constructors are explicitly declared. So the implicitly declared default
constructor will invoke all base and member default constructors. Therefore
the exception specification of C's implicitly generated constructor must
allow
any exception that the base or member default constructor may emit.

Example 19-1b

class Derived;

class Base
{
public:
  virtual Base& 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 defines four functions
  Derived::Derived();
  Derived::Derived(const Derived&);
  Derived& Derived::operator=(const Derived&) throw(B1, M1);  // error
  Derived::~Derived() throw(B2, M2);    // error
};

The last two functions are illegal because any override inherited virtual
function
must have an exception specification at least as restrictive as the version
in
the base class.
If this were not the case-:
Base *p = new Derived;
// destruction can throw B2 or M2,
// even though Base::~Base() specified it
// would only throw B2.
delete p;

Moral: Do not use exception specifications

The Four Implicitly Generated Functions
========================================
Implicit Default Constructor
-----------------------------

Generated if you do not declare one of your own.
It is public and inline.
It is only defined if you call it.
It has the same effect as if you defined an empty default constructor
yourself.
Can throw anything a base or member function constructor can throw.

Implicit Copy Constructor
-----------------------------
Generated if you do not declare one of your own.
It is public and inline.

It is only defined if you call it.

It's parameter is a const reference if every base and member has a
  copy constructor that takes a const reference parameter
  else it takes a reference to non-const.

It is only implicitly defined if a copy of an object of that type is to be
made.

The implicitly defined copy performs a memberwise copy of it's base and
member
objects, and can throw anything that a base or member copy constructor can
throw.
It is illegal if any base or member has an inaccessible or ambiguous copy
constructor.

Implicit Copy Assignment Operator
-----------------------------------
Generated if you do not declare one of your own.
It is public and inline.

It returns a non-const reference to the assigned-to object,
it's parameter is a reference to const if every base and member has
  a copy assignment operator that has a const reference parameter,
  else the parameter will be a reference to non-const.

It is only implicitly defined if an assignment of an object of given type is
to be made.

The implicitly defined copy assignment performs a memberwise copy of it's
base and member
  objects (including possibly multiple assignments of it's virtual base
objects),
  and can throw anything that a base or member copy constructor can throw.

It is illegal if any base or member is const, is a reference, or has a
illegal or
  ambiguous copy assignment operator.


Implicit Destructor
--------------------
Generated if you do not declare one of your own.
It is public and inline.

It is only implicitly defined if the destruction of an object is about to
occur
Has the same effect as an empty destructor you had written.
Can throw anything a base or member destructor can throw.

It is illegal if any base or member has an inaccessible destructor,
or any base destructor is virtual and not all base and member destructors
have the
same exception specifications.


An auto_ptr member
--------------------

class X {
  auto_ptr<int> i_;
};

Note-: Prefer shared_ptr to auto_ptr. shared_ptr is being added to the C++
standard library.

For class X, these are the implicitly declared public functions-:
  inline X::X() throw();  :  i_() {}
  inline X::X(X& other) throw()   :  i_(other.i_) {}
  inline X& X::operator=(X& other) throw()   : { i_ = other.i_; return
*this; }
  inline X::~X() throw();    {}

Note that the copy constructor and copy assignment operators take references
to
non-const - that's what auto_ptr versions do. Also these two functions
transfer
ownership. If that is not what is required, the class must provide it's own
versions of these functions.
Also, all functions have a throw nothing exception specification because no
auto_ptr operation can throw anything.


Derived Class Problems
=======================

// Count is to keep track of all derived objects created.
// Virtual inheritance ensures that only one base is defined
// in case of multiple inheritance.
// Derived classes inherit virtually, and all derived class
// constructors will call the Count constructor with special
// parameters.
class Count {
public:
  Count( /* special parameters */ );
  Count& operator=(const Count&);
  virtual ~Count();
private:
//Undefined - no copy constructor
//Want derived classes to use the special constructor above.
  Count(const Count&);
};

Using the base class thus-:

class BadDerived : private virtual Count {
  int i_;
  ...
};

Creating a BadDerived object, the implicit definition of the
default constructor takes place. This fails because the base class
default constructor (called by BadDerived's default constructor)
does not exist.

The default copy constructor when defined will not call the
special Count constructor. It will call Count's implicitly
generated copy constructor.

If Count's implicitly generated copy constructor is suppressed
then the BadDerived generated copy constructor definition will
fail at run-time: it cannot access it's base class copy constructor.

The implicitly generated copy assignment operator and destructor
will invoke the base class versions as required.

To prevent errors such as with BadDerived, the next section proposes
some rules so that a preferable compile time error, or at least a
run time error occurs if the derived class is not coded correctly.

Enforcing Rules for Derived Classes
How can the author of a base class ensure that authors of
derived classes to explicitly write each of the four basic
functions?

class Base {
public:
  virtual ~Base();
private:
  Base(const Base&);
  Base& operator=(const Base&);
};

The above Base class has no default constructor
( an implicitly generated default constructor
  will not be declared if a constructor has been declared)
and it has a hidden copy constructor and copy assignment operator.

It's not possible to hide the destructor, it must be accessible to
derived classes as public virtual or non-virtual protected (Item 18).

Using the above recommended technique shown in the Base class above at least
ensures that three of the four principle class operations produce
compile time errors if they are not defined in the derived class.




More information about the Effective-cpp mailing list