[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;
}
posted @ 2015-10-22 23:31  treeland  阅读(172)  评论(0编辑  收藏  举报