复制构造函数、赋值操作符和析构函数总称为复制控制。
复制构造函数:特殊的构造函数,具有单个形参,该形参时对该类类型的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;
}