c++ - What is The Rule of Three? -


what copying object mean? copy constructor , copy assignment operator? when need declare them myself? how can prevent objects being copied?

introduction

c++ treats variables of user-defined types value semantics. means objects implicitly copied in various contexts, , should understand "copying object" means.

let consider simple example:

class person {     std::string name;     int age;  public:      person(const std::string& name, int age) : name(name), age(age)     {     } };  int main() {     person a("bjarne stroustrup", 60);     person b(a);   // happens here?     b = a;         // , here? } 

(if puzzled name(name), age(age) part, called member initializer list.)

special member functions

what mean copy person object? main function shows 2 distinct copying scenarios. initialization person b(a); performed copy constructor. job construct fresh object based on state of existing object. assignment b = a performed copy assignment operator. job little more complicated, because target object in valid state needs dealt with.

since declared neither copy constructor nor assignment operator (nor destructor) ourselves, these implicitly defined us. quote standard:

the [...] copy constructor , copy assignment operator, [...] , destructor special member functions. [ note: the implementation implicitly declare these member functions class types when program not explicitly declare them. implementation implicitly define them if used. [...] end note ] [n3126.pdf section 12 §1]

by default, copying object means copying members:

the implicitly-defined copy constructor non-union class x performs memberwise copy of subobjects. [n3126.pdf section 12.8 §16]

the implicitly-defined copy assignment operator non-union class x performs memberwise copy assignment of subobjects. [n3126.pdf section 12.8 §30]

implicit definitions

the implicitly-defined special member functions person this:

// 1. copy constructor person(const person& that) : name(that.name), age(that.age) { }  // 2. copy assignment operator person& operator=(const person& that) {     name = that.name;     age = that.age;     return *this; }  // 3. destructor ~person() { } 

memberwise copying want in case: name , age copied, self-contained, independent person object. implicitly-defined destructor empty. fine in case since did not acquire resources in constructor. members' destructors implicitly called after person destructor finished:

after executing body of destructor , destroying automatic objects allocated within body, destructor class x calls destructors x's direct [...] members [n3126.pdf 12.4 §6]

managing resources

so when should declare special member functions explicitly? when our class manages resource, is, when object of class responsible resource. means resource acquired in constructor (or passed constructor) , released in destructor.

let go in time pre-standard c++. there no such thing std::string, , programmers in love pointers. person class might have looked this:

class person {     char* name;     int age;  public:      // constructor acquires resource:     // in case, dynamic memory obtained via new[]     person(const char* the_name, int the_age)     {         name = new char[strlen(the_name) + 1];         strcpy(name, the_name);         age = the_age;     }      // destructor must release resource via delete[]     ~person()     {         delete[] name;     } }; 

even today, people still write classes in style , trouble: "i pushed person vector , crazy memory errors!" remember default, copying object means copying members, copying name member merely copies pointer, not character array points to! has several unpleasant effects:

  1. changes via a can observed via b.
  2. once b destroyed, a.name dangling pointer.
  3. if a destroyed, deleting dangling pointer yields undefined behavior.
  4. since assignment not take account name pointed before assignment, sooner or later memory leaks on place.

explicit definitions

since memberwise copying not have desired effect, must define copy constructor , copy assignment operator explicitly make deep copies of character array:

// 1. copy constructor person(const person& that) {     name = new char[strlen(that.name) + 1];     strcpy(name, that.name);     age = that.age; }  // 2. copy assignment operator person& operator=(const person& that) {     if (this != &that)     {         delete[] name;         // dangerous point in flow of execution!         // have temporarily invalidated class invariants,         // , next statement might throw exception,         // leaving object in invalid state :(         name = new char[strlen(that.name) + 1];         strcpy(name, that.name);         age = that.age;     }     return *this; } 

note difference between initialization , assignment: must tear down old state before assigning name prevent memory leaks. also, have protect against self-assignment of form x = x. without check, delete[] name delete array containing source string, because when write x = x, both this->name , that.name contain same pointer.

exception safety

unfortunately, solution fail if new char[...] throws exception due memory exhaustion. 1 possible solution introduce local variable , reorder statements:

// 2. copy assignment operator person& operator=(const person& that) {     char* local_name = new char[strlen(that.name) + 1];     // if above statement throws,     // object still in same state before.     // none of following statements throw exception :)     strcpy(local_name, that.name);     delete[] name;     name = local_name;     age = that.age;     return *this; } 

this takes care of self-assignment without explicit check. more robust solution problem copy-and-swap idiom, not go details of exception safety here. mentioned exceptions make following point: writing classes manage resources hard.

noncopyable resources

some resources cannot or should not copied, such file handles or mutexes. in case, declare copy constructor , copy assignment operator private without giving definition:

private:      person(const person& that);     person& operator=(const person& that); 

alternatively, can inherit boost::noncopyable or declare them deleted (c++0x):

person(const person& that) = delete; person& operator=(const person& that) = delete; 

the rule of three

sometimes need implement class manages resource. (never manage multiple resources in single class, lead pain.) in case, remember rule of three:

if need explicitly declare either destructor, copy constructor or copy assignment operator yourself, need explicitly declare 3 of them.

(unfortunately, "rule" not enforced c++ standard or compiler aware of.)

advice

most of time, not need manage resource yourself, because existing class such std::string you. compare simple code using std::string member convoluted , error-prone alternative using char* , should convinced. long stay away raw pointer members, rule of 3 unlikely concern own code.


Comments