Вы находитесь на странице: 1из 6

01/12/2010 Dr Dobbs - C++ Made Easier: The Rule…

Subscribe
Newsletters
RSS
Log in
Home Articles News Blogs Source Code Dobbs on DVD Dobbs TV Webinars & Events Go Parallel

CHANNELS 0 Like  Permalink

Architecture & Design


C/C++ C++ Made Easier: The Rule of Three Recent Articles
Database By Andrew Koenig and Barbara E. Moo, June 01, 2001
Jolt Productivity Awards: Development Environments
Development Tools 0 Comments #1
Embedded Systems What Developers Think: Then and Now
What you leave out of a class can cause you just as much Silverlight's Validation Fix
High Performance trouble as what you put in. This simple rule will save you the
Q&A: Why Coders Write Open Source
Computing
most common mistakes.
Managing Open Source Licensing
Java
Introduction
Jolt Awards
Most Popular
Mobility It seems that every week or two, someone posts a programming
example to the c omp.lang.c ++ newsgroup on Usenet that involves Stories Blogs
Open Source a program that fails bec ause of a class that is missing a copy
Security c onstructor or an assignment operator. Invariably, the trouble with Java and C++ Socket Communication
the c lass in question is obvious to anyone who understands the The NoSQL Alternative
Web Development Rule of Three. Augmented Reality on Mobile Internet Devices
Performance Library Basics
Windows/.NET
The Rule of Three [1] says that there are three member func tions The Boost.Threads Library
Visual Studio 2010 that go together: the destructor, the c opy c onstructor, and the
assignment operator. A class that defines a destructor should
almost always define the other two members. Moreover, a c lass
that defines a copy constructor or assignment operator should
INFO-LINK usually define the other two members as well [2].

This article explains the reasoning behind the Rule of Three.

An Example and a Misconception

Let’s take a look at a c lass that someone might write as part of an


effort to understand how the standard-library vector class works.
Indeed, before implementations of the standard library were widely
available, we used to use a class muc h like this one in the summer
c ontinuing-education course that we taught at Stanford:

// This class contains a subtle error


class IntVec {
public:
IntVec(int n): data(new int[n]) { }
~IntVec() { delete[] data; };
int& operator[](int n)
{ return data[n]; }
const int& operator[](int n) const
{ return data[n]; }

private:
int* data;
};
View All Videos

This c lass attempts to simplify the vector c lass by dealing only


with arrays of integers, and by not worrying about the ability of
vectors to grow and shrink on command. Constructing an IntVec
object alloc ates enough memory for the given number of integers,
Most Recent Premium Content
destroying the object dealloc ates the memory, and indexing it Whitepapers
acc esses the given element.
The Private Fiber-Optic Network Advantage
The trouble with this example is that it has simplified matters too Improving Availability in a Digital Hospital: Balancing
much — to the extent that copying an objec t of this class will High Levels of IT Availability with Energy Efficiency
c ause serious trouble: Ten Steps to Increasing Data Center Efficiency and
Availability through Infrastructure Monitoring
int main()
{
IntVec x(100);
IntVec y = x; // Trouble!
return 0;
}
This Month's Digital Digest Edition
The reason for the trouble is that the IntVec class does not
explicitly define a c opy constructor. When a c lass does not define a
c opy c onstructor, the implementation synthesizes one, defining
c opying an object of the c lass in terms of c opying the members of
the c lass. In other words, when we initialize y as a copy of x, the
implementation handles that definition by initializing y.data to be a
c opy of x.data. Forming this c opy is not harmful by itself.
However, when the program terminates, the local variables x and y
will both be destroyed, whic h will result in exec uting delete[] on
x.data and y.data. Because x.data and y.data have the same
value, these two delete[] operations will try to free the same

drdobbs.com/184401400;jsessionid=JI… 1/6
01/12/2010 Dr Dobbs - C++ Made Easier: The Rule…
memory twic e, the effec t of which is undefined.

We have seen the claim that the sign of trouble in the IntVec c lass
is that it c ontains a pointer, and that classes that have pointers as
data members should have copy construc tors. We disagree with
this claim, having c ome to realize that the pointer by itself is
harmless. After all, a pointer is just a value. Why should there be
anything intrinsically more harmful about copying a pointer than
about copying any other value?

The true sign of trouble in this class is that the c lass has a
destructor. In almost all cases, when a c lass has a destructor, it
also needs a c opy c onstructor and an assignment operator. To see
that it is the destructor that is fundamental, try removing it. Doing
so c reates a memory leak, of course, but it also avoids the trouble
assoc iated with multiple deletion. If you are willing to tolerate the
memory leak that stems from the lack of a destructor, there is no
additional reason why this class needs a copy construc tor or
assignment operator.

Fixing Our Class

Having written a class that requires a c opy c onstructor and an


assignment operator, how shall we define those members? If we
don’t know what else to do, we can punt:

// This class corrects the error


// by brute force
class IntVec {
public:
IntVec(int n): data(new int[n]) { }
~IntVec() { delete[] data; };
int& operator[](int n)
{ return data[n]; }
const int& operator[](int n) const
{ return data[n]; }

private:
int* data;

// these two member functions added


IntVec(const IntVec&);
IntVec& operator=(const IntVec&);
};

By dec laring a copy constructor and assignment operator explicitly,


we inhibit the c ompiler’s built-in versions of those func tions. By
making the copy constructor and assignment operator private, we
prevent anyone from using them. Bec ause no one can use them,
we don’t have to define them: if a member function is not virtual,
then it needs to be defined only if someone uses it.

This solution to the problem has the great virtue of requiring almost
no thought. As long as no one tries to copy an IntVec, we don’t
need to worry about how to do so. Moreover, any attempts to
c opy an IntVec will be caught during c ompilation.

Of c ourse, we may wish to make our c lass more useful, rather than
merely outlawing operations that we were unwilling to take the
trouble to define. To do so, we must think about what c opying an
IntVec objec t means. There are several possible meanings, the
most straightforward of whic h is probably to say that c opying an
IntVec objec t c opies its contents. In that c ase, we c an define the
c opy c onstructor and assignment operator this way:

// This class corrects the error by


// defining copying and assignment
class IntVec {
public:
IntVec(int n): data(new int[n]), size(n) { }
~IntVec() { delete[] data; };
int& operator[](int n)
{ return data[n]; }
const int& operator[](int n) const
{ return data[n]; }

IntVec(const IntVec& v):


data(new int[v.size]),
size(v.size) {
std::copy(data, data + size, v.data);
}
IntVec&
operator=(const IntVec& v) {
int* newdata = new int[v.size];
std::copy(v.data,v.data+v.size, newdata);
delete[] data;
data = newdata;
size = v.size;
return *this;
}

drdobbs.com/184401400;jsessionid=JI… 2/6
01/12/2010 Dr Dobbs - C++ Made Easier: The Rule…
private:
int* data;
int size;
};

You c an see that our modifications to this little class have more
than doubled its size. These modific ations all stem from our decision
to define the copy constructor and assignment operator in ways
that are consistent with the destructor.

The Reasons for the Requirements

In order to see why destructors require c opy c onstructors and


assignment operators, think about what a destruc tor does: it
c ontains code that is supposed to run whenever an objec t of that
c lass is destroyed. What does this c ode do? It should be c lear that
if the destructor were to affect only the c ontents of the objec t
itself, the destruc tor would be useless — if an object is in the
process of being destroyed, any change to that object will soon be
obliterated. Therefore, for a destructor to be useful, it must affect
a part of the program’s state other than the object itself.

Whatever this effect might be, it must be important. Otherwise,


why bother with a destructor at all? For example, in our IntVec
c lass, the effect is to deallocate the memory that the c onstructor
allocated.

We can c onc lude, then, that if our class has a destructor, that
destructor does something important to the state of the program
outside the class objec t itself. Now, let’s suppose that our c lass
does not have a c opy constructor. Then copying an object of that
c lass will copy all of its data members. When these two objects are
destroyed, the destructor will run twic e. Moreover, the information
available to the destructor in the objec t being destroyed will be the
same in each case, because eac h data member of the copy will be
a c opy of the c orresponding data member of the original. We
already know that we care whether the destruc tor was exec uted
onc e or not at all. It is therefore highly likely that we also c are
whether the destructor is executed twice, as opposed to onc e. Yet
if we do not have a copy c onstructor, copying an object will cause
a destruc tor that would otherwise have been executed onc e to be
exec uted twic e under the same conditions. Suc h duplic ate
exec utions are a recipe for trouble.

A similar argument lets us conc lude that a c lass with a destruc tor
must also have an assignment operator. If such a class does not
have an explicitly defined assignment operator, assigning one
object of that class to another will assign all the source’s data
members to the c orresponding data members of the destination.
After that assignment, the destination’s data members will be
c opies of the sourc e’s data members, and the destructor will be in
exac tly the same kind of trouble that it would have been in had the
object been copied rather than assigned.

Although a c lass with a destruc tor almost always needs a copy


c onstructor and an assignment operator, the reverse is not always
true. The reason is that not every copy construc tor or assignment
operator alloc ates resources, so not every c opy c onstructor or
assignment operator requires a destructor in order to free those
resourc es. For example, a c opy c onstructor might exist bec ause it
does less work than the default copy construc tor would do. As an
example of such a c opy c onstructor, consider a class eac h instanc e
of which contains data (of type Data) and a cache (of type
Cache):

class Thing {
public:
Thing() { /* ... */ }
Thing(const Thing& t):
data(t.data)
{ } // don't copy the cache
Thing& operator=(const Thing& t) {
data = t.data; // copy the data

// clear the cache


cache = Cache();
}
private:
Data data;
Cache cache;
};

Here, the c opy c onstructor exists in order to prevent the cache


from being c opied, which the default c opy c onstructor would do.
Similarly, the assignment operator explicitly c lears the cache rather
than assigning it from t.cache. It should not be hard to imagine
that suc h a class might legitimately be able to do without an
explicit destruc tor.

However, if a c opy c onstructor does anything that is not

drdobbs.com/184401400;jsessionid=JI… 3/6
01/12/2010 Dr Dobbs - C++ Made Easier: The Rule…
ephemeral, the c lass will need a destructor. For example, a class
with a c opy c onstructor that alloc ates a resourc e will need a
destructor that deallocates that resource, unless the author is
willing for the class to leave the resources alloc ated.

Another example of a c lass that requires a copy construc tor and


assignment operator is one in which one part of the class object
refers to another part. For example, c onsider a class that
represents a string with a marker somewhere in it:

class String_with_marker {
// ...
private:
std::string str;
int marker;
};

Here, the marker member represents the index of the character at


which the marker is located. From what we’ve shown here about
this class, there is no need for a copy c onstructor or assignment
operator. However, suppose that instead of using an integer, we
want to use an iterator to mark the position:

class String_with_marker {
// ...
private:
std::string str;
// changed
std::string::iterator marker;
};

Now, although we still do not need a destructor, we absolutely do


need a copy construc tor and an assignment operator. Otherwise,
c opying an object of this class will c opy both the string and the
c orresponding iterator, and copying the iterator will yield an iterator
that still refers to an element of the original string. We might define
these members as follows:

class String_with_marker {
// ...
public:
// added
String_with_marker(const String_with_marker& s):
str(s.str),
marker(str + (s.marker - s.str.begin())) { }
String_with_marker& operator=(const String_with_marker& s)
{
str = s.str;
marker = str + (s.marker - s.str);
return *this;
}

private:
std::string str;
std::string::iterator marker;
};

Conclusion

The Rule of Three is really two rules:

If a class has a nonempty destructor, it almost always


needs a copy construc tor and an assignment operator.
If a class has a nontrivial copy construc tor or assignment
operator, it usually needs both of these members and a
destruc tor as well.

A destructor usually dealloc ates a resourc e. If the resourc e needs


explicit deallocation, it is a good bet that the implementation-
generated c opy c onstructor and assignment operator will not
allocate another instanc e of that resource properly. Therefore,
whenever a class has a nontrivial destructor, you should assume
that a c opy c onstructor and assignment operator are required
unless you can prove otherwise. Similarly, if you have a copy
c onstructor or assignment operator, you probably need both of
them and a destructor too.

Some resources cannot easily be copied. For example, imagine a


c lass that is designed to ensure exc lusive ac cess to a devic e. Such
a c lass would set a loc k in its construc tor and clear it in its
destructor. How would it make sense to copy such a class? Its
whole purpose is to ensure exclusivity.

Designing a copy construc tor for a class might be more trouble than
it is worth. Consider our IntVec class, where adding a copy
c onstructor and assignment operator more than doubled the size of
the c lass.

If you need a copy construc tor or assignment operator, and you


c an’t find a reasonable way to implement them or don’t want to do

drdobbs.com/184401400;jsessionid=JI… 4/6
01/12/2010 Dr Dobbs - C++ Made Easier: The Rule…
so, c onsider making them private so that programmers won’t try to
use them by ac cident. In all other cases, you should write an
assignment operator and c opy c onstructor whenever you write a
destructor.

Notes

[1] Marshall Cline coined the term in 1991.

[2] Classes often have an empty virtual destruc tor, which is there
only bec ause it is virtual and not to do any actual work. Such
destructors don’t count for the purpose of this disc ussion.

Andrew Koenig is a member of the Large-Sc ale Programming


Research Department at AT&T’s Shannon Laboratory, and the
Project Editor of the C++ standards c ommittee. A programmer for
more than 30 years, 15 of them in C++, he has published more
than 150 articles about C++ and speaks on the topic worldwide. He
is the author of C Traps and Pitfalls and co-author of Ruminations
on C++.

Barbara E. Moo is an independent consultant with 20


years’ experience in the software field. During her
nearly 15 years at AT&T, she worked on one of the
first commercial projects ever written in C++,
managed the company’s first C++ compiler project,
and directed the development of AT&T’s award-
winning WorldNet Internet service business. She is co-
author of Ruminations on C++ and lectures
worldwide.

Care to Comment?
Subject (max length: 75):

Comment:

Captcha:

Type the characters you see in the picture above.

Around the Web


An Events Based Algorithm for Distributing
Concurrent Tasks on Multi-Core
Architectures
Here's a programming model which enables scalable parallel
performance on multi-core shared memory architectures.
Quick Read

Swarm: A True Distributed Programming


Language
The Swarm prototype is a simple stack-based language, akin to a
primitive version of the Java bytecode interpreter.
Quick Read

drdobbs.com/184401400;jsessionid=JI… 5/6
01/12/2010 Dr Dobbs - C++ Made Easier: The Rule…

Key Software Development Trends


Several trends are emerging within the area of software
development. Here are some of the most important trends S.
Somasegar has been thinking about recently.
Quick Read

Understanding Parallel Performance


Understanding parallel performance. How do you know when good
is good enough?
Quick Read

Short and Tweet: Experiments on


Recommending Content from Information
Streams
The authors used 12 algorithms to study the URL recommendation
on Twitter as a means of better directing attention in information
streams.
Quick Read

Enabling People and Organizations to Harness the Transformative Power of Technology

CIOs & IT Professionals Software Developers Vertical Markets Global Communications Most Popular
Service Providers
Black Hat Dr. Dobbs Advanced Trading ANTenna
Cloud Connect Dr. Dobbs M-Dev Bank Systems & Technology Heavy Reading Bob Evans' Global CIO
Dark Reading Dr. Dobbs Digest InformationWeek Government Heavy Reading Insiders Cable Catchup
Enterprise 2.0 Dr. Dobb's Update InformationWeek Healthcare Pyramid Research David Berlind's Tech Radar
Enterprise Connect TechWeb.com Insurance & Technology Light Reading Digital Life
Enterprise Efficiency Light Reading / Telecom Light Reading Mobile Evil Bytes
Web & Digital Professionals
HDI Wall Street & Technology Light Reading Cable InformationWeek Analytics
Internet Evolution Light Reading Europe
InformationWeek My Interop
Web 2.0 Expo Game Industry Professionals
InformationWeek 500 Light Reading Asia Jon Erickson's Blog
Web 2.0 Summit Gamasutra Ethernet Expo
InformationWeek 500 Conference Microsoft/Window s Blog
TechWeb.com Game Developers Conference (GDC) TelcoTV
InformationWeek Analytics Monkey Bidness
Independent Games Festival Tow er Summit
InformationWeek Events Government Officials Over the Air
Game Developer Magazine Light Reading Live & Virtual Events
InformationWeek Global CIO Gov 2.0 Expo The Philter
GDC Europe Webinars
InformationWeek Healthcare Gov 2.0 Summit Valley Wonk
GDC China
InformationWeek India GTEC Wolfe's Den
Game Career Guide
InformationWeek SMB InformationWeek Government
Game Advertising Online
Intelligent Enterprise TechWeb.com
Interop
Netw ork Computing
No Jitter
Plug into the Cloud
TechWeb.com

UBM TechWeb Reader Services


About UBM TechWeb Advertising Contacts Technology Marketing Solutions Contact Us Feedback

Reprints TechWeb Digital Library / White Papers TechWeb Events Calendar TechWeb.com

Terms of Service | Privacy Statement | Copyright © 2010 UBM TechWeb, All rights reserved.

Pow ered by Zend PHP

drdobbs.com/184401400;jsessionid=JI… 6/6

Вам также может понравиться