www.gotw.ca/gotw/069.htm
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. 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? 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. 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?
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. 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: Illustrating the exception // specifications of implicitly declared functions // class C // ... 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(); Consider: What if one of the implicitly generated functions overrides an inherited virtual function?
before even // thinking about trying anything like this: // virtual Base& /* or Derived& */ operator=( const Derived& ) throw( B1 ); 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 were allowed, consider the code: Base* p = new Derived; 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.
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." 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. 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 only implicitly defined when you actually ...
|