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 beingness 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.)
what mean re-create person object? main function shows 2 distinct copying scenarios. initialization person b(a); performed copy constructor. job build 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 re-create constructor nor assignment operator (nor destructor) ourselves, these implicitly defined us. quote standard:
the [...] re-create constructor , re-create assignment operator, [...] , destructor special fellow member functions. [ note: the implementation implicitly declare these fellow member functions class types when programme 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 re-create constructor non-union class x performs memberwise re-create of subobjects. [n3126.pdf section 12.8 §16]
the implicitly-defined re-create assignment operator non-union class x performs memberwise re-create assignment of subobjects. [n3126.pdf section 12.8 §30]
implicit definitionsthe implicitly-defined special fellow member functions person this:
// 1. re-create constructor person(const person& that) : name(that.name), age(that.age) { } // 2. re-create assignment operator person& operator=(const person& that) { name = that.name; age = that.age; homecoming *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 resourcesso when should declare special fellow 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 fellow member simply copies pointer, not character array points to! has several unpleasant effects:
a can observed via b. once b destroyed, a.name dangling pointer. if a destroyed, deleting dangling pointer yields undefined behavior. since assignment not take business relationship name pointed before assignment, sooner or later memory leaks on place. explicit definitions since memberwise copying not have desired effect, must define re-create constructor , re-create assignment operator explicitly create deep copies of character array:
// 1. re-create constructor person(const person& that) { name = new char[strlen(that.name) + 1]; strcpy(name, that.name); age = that.age; } // 2. re-create assignment operator person& operator=(const person& that) { if (this != &that) { delete[] name; // unsafe 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; } homecoming *this; } note difference between initialization , assignment: must tear downwards 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.
unfortunately, solution fail if new char[...] throws exception due memory exhaustion. 1 possible solution introduce local variable , reorder statements:
// 2. re-create 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 next statements throw exception :) strcpy(local_name, that.name); delete[] name; name = local_name; age = that.age; homecoming *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 create next point: writing classes manage resources hard.
noncopyable resourcessome resources cannot or should not copied, such file handles or mutexes. in case, declare re-create constructor , re-create 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, re-create constructor or re-create assignment operator yourself, need explicitly declare 3 of them.
(unfortunately, "rule" not enforced c++ standard or compiler aware of.)
advicemost of time, not need manage resource yourself, because existing class such std::string you. compare simple code using std::string fellow member convoluted , error-prone alternative using char* , should convinced. long remain away raw pointer members, rule of 3 unlikely concern own code.
c++ copy-constructor assignment-operator c++-faq rule-of-three
No comments:
Post a Comment