《C++ primer》第 13 章 复制控制

来源:互联网 发布:软件开发上线流程图 编辑:程序博客网 时间:2024/06/02 23:07

概述

  1. 类除了需要定义对象上的操作,还需要定义复制、赋值或是撤销该类型对象的含义。特殊的成员函数包括复制构造函数、赋值操作符和析构函数可用于定义这些操作。这些被称为复制控制函数
  2. 如果类没有定义其中的一个或是几个,编译器将自动定义它们,由编译器合成的复制控制,函数非常精炼,但有些情况依赖于默认定义将会导致灾难
  3. 定义复制控制函数最为困难的部分在于认识到它们的必要性
  4. 复制构函数:具有单个形参,该形参(常用const修饰)是对该类型的引用。
  5. 析构函数:当对象超出作用域或动态分配的对象被删除的时候将自动调用析构函数。编译器会自动执行非static数据的析构函数

复制构造函数

  1. 直接初始化直接调用与实参相匹配的构造函数,复制初始化总是调用复制构造函数
    string null_book = "abcd";      // 复制初始化string book("旧制度与大革命");  // 直接初始化

  2. 对于不能支持复制的类型或者使用explict定义的构造函数,直接初始化与复制初始化有差异。例如对于IO类型不能进行复制初始化
  3. 当形参为非引用类型,同样以非引用类型作为返回值的时候,由复制构造函数进行复制得到副本
  4. 与合成的默认构造函数不同的是,即使我们定义了其它复制构造函数,也会合成复制构造函数。它将逐个成员初始化(目前来看,合成复制的构造函数肯定会生成,但是不一定会运行,存疑
  5. 合成的默认构造函数对于数组的复制比较特别:将复制数组中的每一个元素,而不是数组名指针
  6. 必须定义复制构造函数的情况:1有数组成员是指针 2有成员在构造函数中分配其它资源
  7. 有些类需要禁止复制(iostream),类必须显示的声明其复制构造函数为private。但是友元和成员仍然可以复制,如果连这些也要禁止的话,那么只声明不定义。
  8. 大多数类应定义复制构造函数和默认构造函数。不允许复制的类对象只能作为引用传递给函数或者从函数返回,不能用作容器的元素

赋值操作符

  1. 内置类型的赋值运算返回对右操作数的引用,因此赋值运算也返回对同一类型的引用
  2. 复制构造函数与赋值常一起使用

析构函数

  1. 动态分配的对象只有在指向该对象的指针被删除时才撤销。如果没有删除指向动态对象的指针,则不会运行该对象的析构函数,对象一直存在,从而导致内存泄露,而且对象内部使用的资源也不会释放。
  2. 当对象的引用或指针超出作用域的时候,不会运行析构函数,只有删除指向动态分配对象的指针或是实际对象超出作用域,才会调用析构函数
  3. 容器中的元素总是按照逆序的顺序撤销的。
  4. 合成的析构函数并不删除指针成员所指向的对象
  5. 析构函数不能重载
  6. 区别于复制构造函数与赋值操作符:即使我们定义了析构函数,合成析构函数仍然运行
  7. 经验三法则:如果类需要析构函数,则它也需要赋值操作符和复制构造函数。

消息处理实例

  1. 这是一个需要分配资源的实例,使用默认的合成函数达不到预期效果
  2. 注意给对象给自己赋值的情况,在这样的情况下,赋值操作中需要检查
    class Message{public:Message(const std::string &str = " "):contents(str){}Message(const Message&);//copy controlMessage& operator=(const Message&);//赋值~Message();void save(Folder &);void remove(Fold &);private:std::string contentsstd::set<Fold*>folders;void put_Msg_in_Folds(const std::set<Fold*>&);void remove_Msg_from_Folders();}//copy controlMessage::Message(const Message&m):contents(m.contents),folders(m.folders){put_Msg_in_Folds(folers);//分配资源,将contents与folder对应}Message& Message::operator=(const Message&rhs){if(&rhs != this)//判断是否给自己赋值{remove_Msg_from_Folders();contents = rhs.contents;folders = rhs.folders;put_Msg_in_Folds(folers);}return *this;}Messge::~Message(){//系统会自动调用string析构函数释放contents,自动调用set析构函数清除用于保存folders成员的内存remove_Msg_from_Folders();}

管理指针成员

  1. 包括指针的类要特别注意,复制指针时只会复制指针中的地址,而不会复制指针中指向的对象
  2. 指针采取常规指针行为:具有指针成员使用默认构造函数的类具有普通指针的缺陷,两个指针指向同一个对象,其中任何一个都可能改变对象的值,尤其是,类本身无法避免悬垂指针,并且没有办法检测对象是否存在
  3. 定义智能指针:负责删除共享对象,其它情况与普通指针相同

智能指针类

  1. 引用使用计数:每当创建对象时,初始化指针并将计数设置为1。当对象作为另一对象的副本而创建时,复制构造函数复制指针并增加与之相对应的使用计数器的值
  2. 实现计数类:
    class U_Ptr{friend class HashPtr;int *ip;size_t use;U_Ptr(int *p):ip(p),use(1){}~U_Ptr({delete ip;}}

  3. 计数类的使用:注意其中的析构函数,以及复制构造函数
    class HasPtr{public:HasPtr(int *p,int i):ptr(new U_Ptr(p)),val(i){}HasPtr(const HasPtr &orig):ptr(orig.ptr),val(orig.val){++ptr->use;}HasPtr & operator=(const HasPtr&);~HasPtr(){if(--ptr->use==0)delete ptr;}private:U_Ptr *ptr;int val;}

  4. HasPtr不可能出现指针的悬垂情况
  5. 管理指针成员:具有指针成员的对象一般需要定义复制控制成员,而计数是管理智能指针类的通用技术。

定义值类型

  1. 复制值类型对象时,会得到一个不同的新副本。对副本所做的操作不会反应在原有对象上。string类就是其中的一个例子
  2. HasPtr(const HasPtr &orig):ptr(new int(*orig.ptr)),val(orig.val){}


  3. 赋值操作:
    HasPtr & HasPtr::operator=(const HasPtr&rhs){*ptr = *rhs.ptr;val =rhs.val;return *this;}

  4. 即使将一个对象赋值给它本身,赋值操作符也必须保证是正确的。本例中,即使左右操作数相等,操作本质也是安全的,无需检查自身赋值