[c++primer][13]复制控制
复制构造函数、赋值操作符和析构函数总称为复制控制。
复制构造函数:特殊的构造函数,具有单个形参,该形参时对该类类型的const引用。定义新对象并用同类型对象初始化,显式调用了复制构造函数;将该类型对象传递给函数或从函数返回该类型的对象时,隐式调用了复制构造函数。
析构函数:当对象超出作用域或动态分配的对象被删除时,自动调用析构函数,用于释放在造函数或在对象生命期内获取的资源。
13.1 复制构造函数
对类类型对象来说,
1)直接初始化直接调用与实参匹配的构造函数
2)复制初始化总是调用复制构造函数(复制初始化首先使用指定构造函数创建一个临时对象,然后用复制构造函数将临时对象复制到正在创建的对象)
合成的复制构造函数
如果我们没有定义复制构造,编译器会为我们合成。即使定义了其他构造函数,也会合成。
合成复制构造函数的行为是,执行逐个成员初始化,将新对象初始化为原对象的副本。
定义自己的复制构造函数
只包含类类型成员和内置类型(不是指针)成员的类,无需显式定义复制构造函数;
复制构造函数的定义也可以使用初始化列表,可以在函数体中做任何其他必要工作。
禁止复制
为防止复制,类必须显式声明其复制构造函数为private;如果想要连友元和成员中的复制也禁止,就可以声明为private而不定义。
13.2 赋值操作符
合成赋值操作符
合成赋值操作符执行逐个成员赋值,返回*this,是对左操作数对象的引用。
可以使用合成复制构造函数的类通常也可以使用合成赋值操作符
13.3 析构函数
何时调用析构函数
撤销类对象(超出作用域)会自动调用析构函数。
当对象的引用或指针超出作用域时,不会运行析构函数。只有删除指向动态分配对象的指针或实际对象(而不是引用)超出作用域时,才会运行析构函数。
三法则:如果需要析构函数,则需要所有三个复制控制成员。
合成析构函数
与复制构造和赋值操作符不同,编译器总是(即便自己编写了析构)会为我们合成一个析构函数。合成析构函数按成员在类中声明次序的逆序撤销成员。对于类类型成员,合成析构函数调用该成员的析构函数来撤销该对象。
执行顺序:先执行类定义的析构,再运行合成析构函数
13.5 管理指针成员
管理指针成员的三种方法
1)指针成员采取常规指针型行为。具有指针的所有缺陷但无需特殊的复制控制
使用默认合成复制构造函数
无法避免悬垂指针(指针指向的内存被释放,指针指向一个不复存在的对象)
2)使用智能指针。指针所指向的对象是共享的,但类能够防止悬垂指针
3)类采取值型行为。指针所指向的对象是唯一的。由每个类对象独立管理。
定义智能指针类
使用计数类
// private class for use by HasPtr only class U_Ptr { friend class HasPtr; int *ip; size_t use; U_Ptr(int *p): ip(p), use(1) { } ~U_Ptr() { delete ip; } };
使用计数类的使用
/* smart pointer class: takes ownership of the dynamically allocated * object to which it is bound * User code must dynamically allocate an object to initialize a HasPtr * and must not delete that object; the HasPtr class will delete it */ class HasPtr { public: // HasPtr owns the pointer; pmust have been dynamically allocated HasPtr(int *p, int i): ptr(new U_Ptr(p)), val(i) { } // copy members and increment the use count HasPtr(const HasPtr &orig): ptr(orig.ptr), val(orig.val) { ++ptr->use; } HasPtr& operator=(const HasPtr&); // if use count goes to zero, delete the U_Ptr object ~HasPtr() { if (--ptr->use == 0) delete ptr; } private: U_Ptr *ptr; // points to use-counted U_Ptr class int val; };
赋值与使用计数
HasPtr& HasPtr::operator=(const HasPtr &rhs) { ++rhs.ptr->use; // increment use count on rhs first if (--ptr->use == 0) delete ptr; // if use count goes to 0 on this object, delete it ptr = rhs.ptr; // copy the U_Ptr object val = rhs.val; // copy the int member return *this; }
定义值型类
给指针成员提供值语义,复制值型对象时,会得到一个不同的新副本;要使指针成员表现得像一个值,赋值对象时必须复制指针所指向的对象。
/* * Valuelike behavior even though HasPtr has a pointer member: * Each time we copy a HasPtr object, we make a new copy of the * underlying int object to which ptr points. */ class HasPtr { public: // no point to passing a pointer if we're going to copy it anyway // store pointer to a copy of the object we're given HasPtr(const int &p, int i): ptr(new int(p)), val(i) {} // copy members and increment the use count HasPtr(const HasPtr &orig): ptr(new int (*orig.ptr)), val(orig.val) { } HasPtr& operator=(const HasPtr&); ~HasPtr() { delete ptr; } // accessors must change to fetch value from Ptr object int get_ptr_val() const { return *ptr; } int get_int() const { return val; } // change the appropriate data member void set_ptr(int *p) { ptr = p; } void set_int(int i) { val = i; } // return or change the value pointed to, so ok for const objects int *get_ptr() const { return ptr; } void set_ptr_val(int p) const { *ptr = p; } private: int *ptr; // points to an int int val; };
赋值操作符不需要分配新对象,它只是必须记得给其指针所指向的对象赋新值,而不是给指针本身赋值。(即使要将一个对象赋值给它本身,赋值操作符也必须总是保证正确。)
HasPtr& HasPtr::operator=(const HasPtr &rhs) { // Note: Every HasPtr is guaranteed to point at an actual int; // We know that ptr cannot be a zero pointer *ptr = *rhs.ptr; // copy the value pointed to val = rhs.val; // copy the int return *this; }