Home Learning Chapter 2: Constructors, Destructors, and Assignment Operators Destructors, and Assignment Operators
Chapter 2: Constructors, Destructors, and Assignment Operators Destructors, and Assignment Operators

Chapter 2: Constructors, Destructors, and Assignment Operators Destructors, and Assignment Operators

0
1

Constructors, Chapter 2: Constructors, Destructors, and Assignment Operators Destructors, and Assignment Operators

Almost every class you write will have one or more constructors, a operator=
destructor, and a copy assignment operator. Little wonder. These are
your bread-and-butter functions, the ones that control the fundamental operations of bringing a new object into existence and making sure
it’s initialized, getting rid of an object and making sure it’s properly
cleaned up, and giving an object a new value. Making mistakes in
these functions will lead to far-reaching — and unpleasant — repercussions throughout your classes, so it’s vital that you get them right.
In this chapter, I offer guidance on putting together the functions that
comprise the backbone of well-formed classes.
Item 5: Know what functions C++ silently writes and
calls.
When is an empty class not an empty class? When C++ gets through
with it. If you don’t declare them yourself, compilers will declare their
own versions of a copy constructor, a copy assignment operator, and a
destructor. Furthermore, if you declare no constructors at all, compilers will also declare a default constructor for you. All these functions
will be both public and inline (see Item 30). As a result, if you write
class Empty{};
it’s essentially the same as if you’d written this:
class Empty {
public:
Empty() { … } // default constructor
Empty(const Empty& rhs) { … } // copy constructor
~Empty() { … } // destructor — see below
// for whether it’s virtual
Empty& operator=(const Empty& rhs) { … } // copy assignment operator
};
Constructors, Chapter 2: Constructors, Destructors, and Assignment Operators
Destructors, and
Assignment Operators
ptg7544714
Constructors, Destructors, operator= Item 5 35
These functions are generated only if they are needed, but it doesn’t
take much to need them. The following code will cause each function
to be generated:
Empty e1; // default constructor;
// destructor
Empty e2(e1); // copy constructor
e2 = e1; // copy assignment operator
Given that compilers are writing functions for you, what do the functions do? Well, the default constructor and the destructor primarily
give compilers a place to put “behind the scenes” code such as invocation of constructors and destructors of base classes and non-static
data members. Note that the generated destructor is non-virtual (see
Item 7) unless it’s for a class inheriting from a base class that itself
declares a virtual destructor (in which case the function’s virtualness
comes from the base class).
As for the copy constructor and the copy assignment operator, the
compiler-generated versions simply copy each non-static data member of the source object over to the target object. For example, consider a NamedObject template that allows you to associate names with
objects of type T:
template<typename T>
class NamedObject {
public:
NamedObject(const char *name, const T& value);
NamedObject(const std::string& name, const T& value);

private:
std::string nameValue;
T objectValue;
};
Because a constructor is declared in NamedObject, compilers won’t
generate a default constructor. This is important. It means that if
you’ve carefully engineered a class to require constructor arguments,
you don’t have to worry about compilers overriding your decision by
blithely adding a constructor that takes no arguments.
NamedObject declares neither copy constructor nor copy assignment
operator, so compilers will generate those functions (if they are
needed). Look, then, at this use of the copy constructor:
NamedObject<int> no1(“Smallest Prime Number”, 2);
NamedObject<int> no2(no1); // calls copy constructor
ptg7544714
36 Item 5 Chapter 2
The copy constructor generated by compilers must initialize no2.nameValue and no2.objectValue using no1.nameValue and no1.objectValue,
respectively. The type of nameValue is string, and the standard string
type has a copy constructor, so no2.nameValue will be initialized by
calling the string copy constructor with no1.nameValue as its argument.
On the other hand, the type of NamedObject<int>::objectValue is int
(because T is int for this template instantiation), and int is a built-in
type, so no2.objectValue will be initialized by copying the bits in
no1.objectValue.
The compiler-generated copy assignment operator for NamedObject<int> would behave essentially the same way, but in general, compiler-generated copy assignment operators behave as I’ve described
only when the resulting code is both legal and has a reasonable
chance of making sense. If either of these tests fails, compilers will
refuse to generate an operator= for your class.
For example, suppose NamedObject were defined like this, where
nameValue is a reference to a string and objectValue is a const T:
template<typename T>
class NamedObject {
public:
// this ctor no longer takes a const name, because nameValue
// is now a reference-to-non-const string. The char* constructor
// is gone, because we must have a string to refer to.
NamedObject(std::string& name, const T& value);
… // as above, assume no
// operator= is declared
private:
std::string& nameValue; // this is now a reference
const T objectValue; // this is now const
};
Now consider what should happen here:
std::string newDog(“Persephone”);
std::string oldDog(“Satch”);
NamedObject<int> p(newDog, 2); // when I originally wrote this, our
// dog Persephone was about to
// have her second birthday
NamedObject<int> s(oldDog, 36); // the family dog Satch (from my
// childhood) would be 36 if she
// were still alive
p = s; // what should happen to
// the data members in p?
Before the assignment, both p.nameValue and s.nameValue refer to string
objects, though not the same ones. How should the assignment affect
p.nameValue? After the assignment, should p.nameValue refer to the
ptg7544714
Constructors, Destructors, operator= Item 6 37
string referred to by s.nameValue, i.e., should the reference itself be
modified? If so, that breaks new ground, because C++ doesn’t provide
a way to make a reference refer to a different object. Alternatively,
should the string object to which p.nameValue refers be modified, thus
affecting other objects that hold pointers or references to that string,
i.e., objects not directly involved in the assignment? Is that what the
compiler-generated copy assignment operator should do?
Faced with this conundrum, C++ refuses to compile the code. If you
want to support copy assignment in a class containing a reference
member, you must define the copy assignment operator yourself.
Compilers behave similarly for classes containing const members
(such as objectValue in the modified class above). It’s not legal to modify const members, so compilers are unsure how to treat them during
an implicitly generated assignment function. Finally, compilers reject
implicit copy assignment operators in derived classes that inherit
from base classes declaring the copy assignment operator private.
After all, compiler-generated copy assignment operators for derived
classes are supposed to handle base class parts, too (see Item 12), but
in doing so, they certainly can’t invoke member functions the derived
class has no right to call.
Things to Remember
✦ Compilers may implicitly generate a class’s default constructor, copy
constructor, copy assignment operator, and destructor.
Item 6: Explicitly disallow the use of compilergenerated functions you do not want.
Real estate agents sell houses, and a software system supporting such
agents would naturally have a class representing homes for sale:
class HomeForSale { … };
As every real estate agent will be quick to point out, every property is
unique — no two are exactly alike. That being the case, the idea of
making a copy of a HomeForSale object makes little sense. How can
you copy something that’s inherently unique? You’d thus like
attempts to copy HomeForSale objects to not compile:
HomeForSale h1;
HomeForSale h2;
HomeForSale h3(h1); // attempt to copy h1 — should
// not compile!
h1 = h2; // attempt to copy h2 — should
// not compile!
ptg7544714
38 Item 6 Chapter 2
Alas, preventing such compilation isn’t completely straightforward.
Usually, if you don’t want a class to support a particular kind of functionality, you simply don’t declare the function that would provide it.
This strategy doesn’t work for the copy constructor and copy assignment operator, because, as Item 5 points out, if you don’t declare
them and somebody tries to call them, compilers declare them for you.
This puts you in a bind. If you don’t declare a copy constructor or a
copy assignment operator, compilers may generate them for you. Your
class thus supports copying. If, on the other hand, you do declare
these functions, your class still supports copying. But the goal here is
to prevent copying!
The key to the solution is that all the compiler generated functions are
public. To prevent these functions from being generated, you must
declare them yourself, but there is nothing that requires that you
declare them public. Instead, declare the copy constructor and the
copy assignment operator private. By declaring a member function
explicitly, you prevent compilers from generating their own version,
and by making the function private, you keep people from calling it.
Mostly. The scheme isn’t foolproof, because member and friend functions can still call your private functions. Unless, that is, you are
clever enough not to define them. Then if somebody inadvertently
calls one, they’ll get an error at link-time. This trick — declaring member functions private and deliberately not implementing them — is so
well established, it’s used to prevent copying in several classes in
C++’s iostreams library. Take a look, for example, at the definitions of
ios_base, basic_ios, and sentry in your standard library implementation.
You’ll find that in each case, both the copy constructor and the copy
assignment operator are declared private and are not defined.
Applying the trick to HomeForSale is easy:
class HomeForSale {
public:

private:

HomeForSale(const HomeForSale&); // declarations only
HomeForSale& operator=(const HomeForSale&);
};
You’ll note that I’ve omitted the names of the functions’ parameters.
This isn’t required, it’s just a common convention. After all, the functions will never be implemented, much less used, so what’s the point
in specifying parameter names?
With the above class definition, compilers will thwart client attempts
to copy HomeForSale objects, and if you inadvertently try to do it in a
ptg7544714
Constructors, Destructors, operator= Item 6 39
member or a friend function, the linker will complain.
It’s possible to move the link-time error up to compile time (always a
good thing — earlier error detection is better than later) by declaring
the copy constructor and copy assignment operator private not in
HomeForSale itself, but in a base class specifically designed to prevent
copying. The base class is simplicity itself:
class Uncopyable {
protected: // allow construction
Uncopyable() {} // and destruction of
~Uncopyable() {} // derived objects…
private:
Uncopyable(const Uncopyable&); // …but prevent copying
Uncopyable& operator=(const Uncopyable&);
};
To keep HomeForSale objects from being copied, all we have to do now
is inherit from Uncopyable:
class HomeForSale: private Uncopyable { // class no longer
… // declares copy ctor or
}; // copy assign. operator
This works, because compilers will try to generate a copy constructor
and a copy assignment operator if anybody — even a member or friend
function — tries to copy a HomeForSale object. As Item 12 explains, the
compiler-generated versions of these functions will try to call their
base class counterparts, and those calls will be rejected, because the
copying operations are private in the base class.
The implementation and use of Uncopyable include some subtleties,
such as the fact that inheritance from Uncopyable needn’t be public
(see Items 32 and 39) and that Uncopyable’s destructor need not be
virtual (see Item 7). Because Uncopyable contains no data, it’s eligible
for the empty base class optimization described in Item 39, but
because it’s a base class, use of this technique could lead to multiple
inheritance (see Item 40). Multiple inheritance, in turn, can sometimes disable the empty base class optimization (again, see Item 39).
In general, you can ignore these subtleties and just use Uncopyable as
shown, because it works precisely as advertised. You can also use the
version available at Boost (see Item 55). That class is named noncopyable. It’s a fine class, I just find the name a bit un-, er, nonnatural.
Things to Remember
✦ To disallow functionality automatically provided by compilers, declare the corresponding member functions private and give no implementations. Using a base class like Uncopyable is one way to do this.
ptg7544714
40 Item 7 Chapter 2
Item 7: Declare destructors virtual in polymorphic
base classes.
There are lots of ways to keep track of time, so it would be reasonable
to create a TimeKeeper base class along with derived classes for different approaches to timekeeping:
class TimeKeeper {
public:
TimeKeeper();
~TimeKeeper();

};
class AtomicClock: public TimeKeeper { … };
class WaterClock: public TimeKeeper { … };
class WristWatch: public TimeKeeper { … };
Many clients will want access to the time without worrying about the
details of how it’s calculated, so a factory function — a function that
returns a base class pointer to a newly-created derived class object —
can be used to return a pointer to a timekeeping object:
TimeKeeper* getTimeKeeper(); // returns a pointer to a dynamic-
// ally allocated object of a class
// derived from TimeKeeper
In keeping with the conventions of factory functions, the objects
returned by getTimeKeeper are on the heap, so to avoid leaking memory and other resources, it’s important that each returned object be
properly deleted:
TimeKeeper *ptk = getTimeKeeper(); // get dynamically allocated object
// from TimeKeeper hierarchy
… // use it
delete ptk; // release it to avoid resource leak
Item 13 explains that relying on clients to perform the deletion is
error-prone, and Item 18 explains how the interface to the factory
function can be modified to prevent common client errors, but such
concerns are secondary here, because in this Item we address a more
fundamental weakness of the code above: even if clients do everything
right, there is no way to know how the program will behave.
The problem is that getTimeKeeper returns a pointer to a derived class
object (e.g., AtomicClock), that object is being deleted via a base class
pointer (i.e., a TimeKeeper* pointer), and the base class (TimeKeeper)
has a non-virtual destructor. This is a recipe for disaster, because C++
ptg7544714
Constructors, Destructors, operator= Item 7 41
specifies that when a derived class object is deleted through a pointer
to a base class with a non-virtual destructor, results are undefined.
What typically happens at runtime is that the derived part of the
object is never destroyed. If a call to getTimeKeeper happened to return
a pointer to an AtomicClock object, the AtomicClock part of the object
(i.e., the data members declared in the AtomicClock class) would probably not be destroyed, nor would the AtomicClock destructor run. However, the base class part (i.e., the TimeKeeper part) typically would be
destroyed, thus leading to a curious “partially destroyed” object. This
is an excellent way to leak resources, corrupt data structures, and
spend a lot of time with a debugger.
Eliminating the problem is simple: give the base class a virtual
destructor. Then deleting a derived class object will do exactly what
you want. It will destroy the entire object, including all its derived
class parts:
class TimeKeeper {
public:
TimeKeeper();
virtual ~TimeKeeper();

};
TimeKeeper *ptk = getTimeKeeper();

delete ptk; // now behaves correctly
Base classes like TimeKeeper generally contain virtual functions other
than the destructor, because the purpose of virtual functions is to
allow customization of derived class implementations (see Item 34).
For example, TimeKeeper might have a virtual function, getCurrentTime,
which would be implemented differently in the various derived
classes. Any class with virtual functions should almost certainly have
a virtual destructor.
If a class does not contain virtual functions, that often indicates it is
not meant to be used as a base class. When a class is not intended to
be a base class, making the destructor virtual is usually a bad idea.
Consider a class for representing points in two-dimensional space:
class Point { // a 2D point
public:
Point(int xCoord, int yCoord);
~Point();
private:
int x, y;
};
ptg7544714
42 Item 7 Chapter 2
If an int occupies 32 bits, a Point object can typically fit into a 64-bit
register. Furthermore, such a Point object can be passed as a 64-bit
quantity to functions written in other languages, such as C or FORTRAN. If Point’s destructor is made virtual, however, the situation
changes.
The implementation of virtual functions requires that objects carry
information that can be used at runtime to determine which virtual
functions should be invoked on the object. This information typically
takes the form of a pointer called a vptr (“virtual table pointer”). The
vptr points to an array of function pointers called a vtbl (“virtual
table”); each class with virtual functions has an associated vtbl. When
a virtual function is invoked on an object, the actual function called is
determined by following the object’s vptr to a vtbl and then looking up
the appropriate function pointer in the vtbl.
The details of how virtual functions are implemented are unimportant. What is important is that if the Point class contains a virtual
function, objects of that type will increase in size. On a 32-bit architecture, they’ll go from 64 bits (for the two ints) to 96 bits (for the ints
plus the vptr); on a 64-bit architecture, they may go from 64 to 128
bits, because pointers on such architectures are 64 bits in size. Addition of a vptr to Point will thus increase its size by 50–100%! No longer
can Point objects fit in a 64-bit register. Furthermore, Point objects in
C++ can no longer look like the same structure declared in another
language such as C, because their foreign language counterparts will
lack the vptr. As a result, it is no longer possible to pass Points to and
from functions written in other languages unless you explicitly compensate for the vptr, which is itself an implementation detail and
hence unportable.
The bottom line is that gratuitously declaring all destructors virtual is
just as wrong as never declaring them virtual. In fact, many people
summarize the situation this way: declare a virtual destructor in a
class if and only if that class contains at least one virtual function.
It is possible to get bitten by the non-virtual destructor problem even
in the complete absence of virtual functions. For example, the standard string type contains no virtual functions, but misguided programmers sometimes use it as a base class anyway:
class SpecialString: public std::string { // bad idea! std::string has a
… // non-virtual destructor
};
At first glance, this may look innocuous, but if anywhere in an application you somehow convert a pointer-to-SpecialString into a pointer-to-
ptg7544714
Constructors, Destructors, operator= Item 7 43
string and you then use delete on the string pointer, you are instantly
transported to the realm of undefined behavior:
SpecialString *pss =new SpecialString(“Impending Doom”);
std::string *ps;

ps = pss; // SpecialString* ⇒ std::string*

delete ps; // undefined! In practice,
// *ps’s SpecialString resources
// will be leaked, because the
// SpecialString destructor won’t
// be called.
The same analysis applies to any class lacking a virtual destructor,
including all the STL container types (e.g., vector, list, set,
tr1::unordered_map (see Item 54), etc.). If you’re ever tempted to inherit
from a standard container or any other class with a non-virtual
destructor, resist the temptation! (Unfortunately, C++ offers no derivation-prevention mechanism akin to Java’s final classes or C#’s sealed
classes.)
Occasionally it can be convenient to give a class a pure virtual
destructor. Recall that pure virtual functions result in abstract classes
— classes that can’t be instantiated (i.e., you can’t create objects of
that type). Sometimes, however, you have a class that you’d like to be
abstract, but you don’t have any pure virtual functions. What to do?
Well, because an abstract class is intended to be used as a base class,
and because a base class should have a virtual destructor, and
because a pure virtual function yields an abstract class, the solution
is simple: declare a pure virtual destructor in the class you want to be
abstract. Here’s an example:
class AWOV { // AWOV = “Abstract w/o Virtuals”
public:
virtual ~AWOV() = 0; // declare pure virtual destructor
};
This class has a pure virtual function, so it’s abstract, and it has a virtual destructor, so you won’t have to worry about the destructor problem. There is one twist, however: you must provide a definition for the
pure virtual destructor:
AWOV::~AWOV() {} // definition of pure virtual dtor
The way destructors work is that the most derived class’s destructor
is called first, then the destructor of each base class is called. Compil-
ptg7544714
44 Item 8 Chapter 2
ers will generate a call to ~AWOV from its derived classes’ destructors,
so you have to be sure to provide a body for the function. If you don’t,
the linker will complain.
The rule for giving base classes virtual destructors applies only to
polymorphic base classes — to base classes designed to allow the
manipulation of derived class types through base class interfaces.
TimeKeeper is a polymorphic base class, because we expect to be able
to manipulate AtomicClock and WaterClock objects, even if we have only
TimeKeeper pointers to them.
Not all base classes are designed to be used polymorphically. Neither
the standard string type, for example, nor the STL container types are
designed to be base classes at all, much less polymorphic ones. Some
classes are designed to be used as base classes, yet are not designed
to be used polymorphically. Such classes — examples include Uncopyable from Item 6 and input_iterator_tag from the standard library (see
Item 47) — are not designed to allow the manipulation of derived class
objects via base class interfaces. As a result, they don’t need virtual
destructors.
Things to Remember
✦ Polymorphic base classes should declare virtual destructors. If a
class has any virtual functions, it should have a virtual destructor.
✦ Classes not designed to be base classes or not designed to be used
polymorphically should not declare virtual destructors.
Item 8: Prevent exceptions from leaving destructors.
C++ doesn’t prohibit destructors from emitting exceptions, but it certainly discourages the practice. With good reason. Consider:
class Widget {
public:

~Widget() { … } // assume this might emit an exception
};
void doSomething()
{
std::vector<Widget> v;

} // v is automatically destroyed here
When the vector v is destroyed, it is responsible for destroying all the
Widgets it contains. Suppose v has ten Widgets in it, and during
destruction of the first one, an exception is thrown. The other nine
ptg7544714
Constructors, Destructors, operator= Item 8 45
Widgets still have to be destroyed (otherwise any resources they hold
would be leaked), so v should invoke their destructors. But suppose
that during those calls, a second Widget destructor throws an exception. Now there are two simultaneously active exceptions, and that’s
one too many for C++. Depending on the precise conditions under
which such pairs of simultaneously active exceptions arise, program
execution either terminates or yields undefined behavior. In this
example, it yields undefined behavior. It would yield equally undefined
behavior using any other standard library container (e.g., list, set), any
container in TR1 (see Item 54), or even an array. Not that containers
or arrays are required to get into trouble. Premature program termination or undefined behavior can result from destructors emitting
exceptions even without using containers and arrays. C++ does not
like destructors that emit exceptions!
That’s easy enough to understand, but what should you do if your
destructor needs to perform an operation that may fail by throwing an
exception? For example, suppose you’re working with a class for database connections:
class DBConnection {
public:

static DBConnection create(); // function to return
// DBConnection objects; params
// omitted for simplicity
void close(); // close connection; throw an
}; // exception if closing fails
To ensure that clients don’t forget to call close on DBConnection objects,
a reasonable idea would be to create a resource-managing class for
DBConnection that calls close in its destructor. Such resource-managing
classes are explored in detail in Chapter 3, but here, it’s enough to
consider what the destructor for such a class would look like:
class DBConn { // class to manage DBConnection
public: // objects

~DBConn() // make sure database connections
{ // are always closed
db.close();
}
private:
DBConnection db;
};
That allows clients to program like this:
ptg7544714
46 Item 8 Chapter 2
{ // open a block
DBConn dbc(DBConnection::create()); // create DBConnection object
// and turn it over to a DBConn
// object to manage
… // use the DBConnection object
// via the DBConn interface
} // at end of block, the DBConn
// object is destroyed, thus
// automatically calling close on
// the DBConnection object
This is fine as long as the call to close succeeds, but if the call yields
an exception, DBConn’s destructor will propagate that exception, i.e.,
allow it to leave the destructor. That’s a problem, because destructors
that throw mean trouble.
There are two primary ways to avoid the trouble. DBConn’s destructor
could:
■ Terminate the program if close throws, typically by calling abort:
DBConn::~DBConn()
{
try { db.close(); }
catch (…) {
make log entry that the call to close failed;
std::abort();
}
}
This is a reasonable option if the program cannot continue to run
after an error is encountered during destruction. It has the advantage that if allowing the exception to propagate from the destructor
would lead to undefined behavior, this prevents that from happening. That is, calling abort may forestall undefined behavior.
■ Swallow the exception arising from the call to close:
DBConn::~DBConn()
{
try { db.close(); }
catch (…) {
make log entry that the call to close failed;
}
}
In general, swallowing exceptions is a bad idea, because it suppresses important information — something failed! Sometimes,
however, swallowing exceptions is preferable to running the risk of
ptg7544714
Constructors, Destructors, operator= Item 8 47
premature program termination or undefined behavior. For this to
be a viable option, the program must be able to reliably continue
execution even after an error has been encountered and ignored.
Neither of these approaches is especially appealing. The problem with
both is that the program has no way to react to the condition that led
to close throwing an exception in the first place.
A better strategy is to design DBConn’s interface so that its clients have
an opportunity to react to problems that may arise. For example,
DBConn could offer a close function itself, thus giving clients a chance
to handle exceptions arising from that operation. It could also keep
track of whether its DBConnection had been closed, closing it itself in
the destructor if not. That would prevent a connection from leaking. If
the call to close were to fail in the DBConn destructor, however, we’d be
back to terminating or swallowing:
class DBConn {
public:

void close() // new function for
{ // client use
db.close();
closed = true;
}
~DBConn()
{
if (!closed) {
try { // close the connection
db.close(); // if the client didn’t
}
catch (…) { // if closing fails,
make log entry that call to close failed; // note that and
… // terminate or swallow
}
}
}
private:
DBConnection db;
bool closed;
};
Moving the responsibility for calling close from DBConn’s destructor to
DBConn’s client (with DBConn’s destructor containing a “backup” call)
may strike you as an unscrupulous shift of burden. You might even
view it as a violation of Item 18’s advice to make interfaces easy to use
correctly. In fact, it’s neither. If an operation may fail by throwing an
exception and there may be a need to handle that exception, the
exception has to come from some non-destructor function. That’s
ptg7544714
48 Item 9 Chapter 2
because destructors that emit exceptions are dangerous, always running the risk of premature program termination or undefined behavior. In this example, telling clients to call close themselves doesn’t
impose a burden on them; it gives them an opportunity to deal with
errors they would otherwise have no chance to react to. If they don’t
find that opportunity useful (perhaps because they believe that no
error will really occur), they can ignore it, relying on DBConn’s destructor to call close for them. If an error occurs at that point — if close does
throw — they’re in no position to complain if DBConn swallows the
exception or terminates the program. After all, they had first crack at
dealing with the problem, and they chose not to use it.
Things to Remember
✦ Destructors should never emit exceptions. If functions called in a
destructor may throw, the destructor should catch any exceptions,
then swallow them or terminate the program.
✦ If class clients need to be able to react to exceptions thrown during
an operation, the class should provide a regular (i.e., non-destructor) function that performs the operation.
Item 9: Never call virtual functions during
construction or destruction.
I’ll begin with the recap: you shouldn’t call virtual functions during
construction or destruction, because the calls won’t do what you
think, and if they did, you’d still be unhappy. If you’re a recovering
Java or C# programmer, pay close attention to this Item, because this
is a place where those languages zig, while C++ zags.
Suppose you’ve got a class hierarchy for modeling stock transactions,
e.g., buy orders, sell orders, etc. It’s important that such transactions
be auditable, so each time a transaction object is created, an appropriate entry needs to be created in an audit log. This seems like a reasonable way to approach the problem:
class Transaction { // base class for all
public: // transactions
Transaction();
virtual void logTransaction() const = 0; // make type-dependent
// log entry

};
ptg7544714
Constructors, Destructors, operator= Item 9 49
Transaction::Transaction() // implementation of
{ // base class ctor

logTransaction(); // as final action, log this
} // transaction
class BuyTransaction: public Transaction { // derived class
public:
virtual void logTransaction() const; // how to log trans-
// actions of this type

};
class SellTransaction: public Transaction { // derived class
public:
virtual void logTransaction() const; // how to log trans-
// actions of this type

};
Consider what happens when this code is executed:
BuyTransaction b;
Clearly a BuyTransaction constructor will be called, but first, a Transaction constructor must be called; base class parts of derived class
objects are constructed before derived class parts are. The last line of
the Transaction constructor calls the virtual function logTransaction, but
this is where the surprise comes in. The version of logTransaction that’s
called is the one in Transaction, not the one in BuyTransaction — even
though the type of object being created is BuyTransaction. During base
class construction, virtual functions never go down into derived
classes. Instead, the object behaves as if it were of the base type.
Informally speaking, during base class construction, virtual functions
aren’t.
There’s a good reason for this seemingly counterintuitive behavior.
Because base class constructors execute before derived class constructors, derived class data members have not been initialized when
base class constructors run. If virtual functions called during base
class construction went down to derived classes, the derived class
functions would almost certainly refer to local data members, but
those data members would not yet have been initialized. That would
be a non-stop ticket to undefined behavior and late-night debugging
sessions. Calling down to parts of an object that have not yet been initialized is inherently dangerous, so C++ gives you no way to do it.
It’s actually more fundamental than that. During base class construction of a derived class object, the type of the object is that of the base
ptg7544714
50 Item 9 Chapter 2
class. Not only do virtual functions resolve to the base class, but the
parts of the language using runtime type information (e.g.,
dynamic_cast (see Item 27) and typeid) treat the object as a base class
type. In our example, while the Transaction constructor is running to
initialize the base class part of a BuyTransaction object, the object is of
type Transaction. That’s how every part of C++ will treat it, and the
treatment makes sense: the BuyTransaction-specific parts of the object
haven’t been initialized yet, so it’s safest to treat them as if they didn’t
exist. An object doesn’t become a derived class object until execution
of a derived class constructor begins.
The same reasoning applies during destruction. Once a derived class
destructor has run, the object’s derived class data members assume
undefined values, so C++ treats them as if they no longer exist. Upon
entry to the base class destructor, the object becomes a base class
object, and all parts of C++ — virtual functions, dynamic_casts, etc., —
treat it that way.
In the example code above, the Transaction constructor made a direct
call to a virtual function, a clear and easy-to-see violation of this
Item’s guidance. The violation is so easy to see, some compilers issue
a warning about it. (Others don’t. See Item 53 for a discussion of
warnings.) Even without such a warning, the problem would almost
certainly become apparent before runtime, because the logTransaction
function is pure virtual in Transaction. Unless it had been defined
(unlikely, but possible — see Item 34), the program wouldn’t link: the
linker would be unable to find the necessary implementation of Transaction::logTransaction.
It’s not always so easy to detect calls to virtual functions during construction or destruction. If Transaction had multiple constructors, each
of which had to perform some of the same work, it would be good software engineering to avoid code replication by putting the common initialization code, including the call to logTransaction, into a private nonvirtual initialization function, say, init:
class Transaction {
public:
Transaction()
{ init(); } // call to non-virtual…
virtual void logTransaction() const = 0;

private:
void init()
{

logTransaction(); // …that calls a virtual!
}
};
ptg7544714
Constructors, Destructors, operator= Item 9 51
This code is conceptually the same as the earlier version, but it’s more
insidious, because it will typically compile and link without complaint.
In this case, because logTransaction is pure virtual in Transaction, most
runtime systems will abort the program when the pure virtual is
called (typically issuing a message to that effect). However, if logTransaction were a “normal” virtual function (i.e., not pure virtual) with an
implementation in Transaction, that version would be called, and the
program would merrily trot along, leaving you to figure out why the
wrong version of logTransaction was called when a derived class object
was created. The only way to avoid this problem is to make sure that
none of your constructors or destructors call virtual functions on the
object being created or destroyed and that all the functions they call
obey the same constraint.
But how do you ensure that the proper version of logTransaction is
called each time an object in the Transaction hierarchy is created?
Clearly, calling a virtual function on the object from the Transaction
constructor(s) is the wrong way to do it.
There are different ways to approach this problem. One is to turn
logTransaction into a non-virtual function in Transaction, then require
that derived class constructors pass the necessary log information to
the Transaction constructor. That function can then safely call the nonvirtual logTransaction. Like this:
class Transaction {
public:
explicit Transaction(const std::string& logInfo);
void logTransaction(const std::string& logInfo) const; // now a non-
// virtual func

};
Transaction::Transaction(const std::string& logInfo)
{

logTransaction(logInfo); // now a non-
} // virtual call
class BuyTransaction: public Transaction {
public:
BuyTransaction( parameters )
: Transaction(createLogString( parameters )) // pass log info
{ … } // to base class
… // constructor
private:
static std::string createLogString( parameters );
};
ptg7544714
52 Item 10 Chapter 2
In other words, since you can’t use virtual functions to call down from
base classes during construction, you can compensate by having
derived classes pass necessary construction information up to base
class constructors instead.
In this example, note the use of the (private) static function createLogString in BuyTransaction. Using a helper function to create a value to
pass to a base class constructor is often more convenient (and more
readable) than going through contortions in the member initialization
list to give the base class what it needs. By making the function static,
there’s no danger of accidentally referring to the nascent BuyTransaction object’s as-yet-uninitialized data members. That’s important,
because the fact that those data members will be in an undefined
state is why calling virtual functions during base class construction
and destruction doesn’t go down into derived classes in the first place.
Things to Remember
✦ Don’t call virtual functions during construction or destruction, because such calls will never go to a more derived class than that of
the currently executing constructor or destructor.

Leave a Reply