Part 6: 拷贝控制(第13章)

// @author:       gr
// @date: 2015-01-08
// @email: forgerui@gmail.com

一、拷贝、赋值与销毁

  1. 拷贝构造函数

  2. 拷贝赋值运算符

  3. 析构函数

    析构函数自身并不直接销毁成员,而是在析构函数之后隐含的析构阶段中被销毁的。

  4. 使用=default修饰成员时,要求编译器生成合成的版本。

  5. 阻止拷贝

    在新标准下,使用删除的函数来阻止拷贝,虽然声明了它,但不能以任何方式使用它。

     struct NoCopy{
    NoCopy() = delete;
    Nocopy(const NoCopy&) = delete; //阻止拷贝
    NoCopy& operator=(const Nocopy&) = delete; //阻止赋值
    };

    将拷贝函数声明为private,阻止调用,并且只声明不实现。

二、拷贝控制和资源管理

  1. 一旦一个类需要析构函数,那么它几乎肯定也需要一个拷贝构造函数和一个拷贝赋值运算符。

  2. 定义拷贝操作,使类的行为看起来像一个值或指针。像值的类拥有自己的状态,像指针的类则共享状态(当拷贝一个这种类的对象时,副本和原对象使用相同的底层数据)。

  3. 行为像值的类,有两个数据成员stringint

     class HasPtr{
    public:
    HasPtr(const std::string &s = std::string()) : ps(new string(s)), i(0){}
    HasPtr(const HasPtr& p) : ps(new string(*p.ps)), i(p.i){}
    HasPtr& operator= (const HasPtr&);
    ~HasPtr(){ delete ps;}
    private:
    int i;
    std::string* ps;
    };
    HasPtr& HasPtr::operator=(const HasPtr& rhs){
    std::string* newp = new std::string(*rhs.ps); //先创建一个新拷贝之后再delete,否则如果传入自己,delete会使rhs.ps失效,再拷贝就什么也没有了。
    delete ps;
    ps = newp;
    i = rhs.i;
    return *this;
    }

    注意:

    1. 如果一个对象赋予它自身,赋值运算符必须能正确工作。
    2. 大多数赋值运算符组合了析构函数构造函数的工作。
  4. 行为像指针的类,多个对象共享一份资源,需要使用引用计数。可以用shared_ptr,也可以自己实现。

     class HasPtr{
    public:
    HasPtr(const std::string& s = std::string()) : ps(new string(s)), use(new std::size_t(1)), i(0){}
    HasPtr(const HasPtr& rhs) : ps(rhs.ps), use(rhs.use), i(rhs.i){ ++*use; }
    HasPtr& operator=(const HasPtr&);
    ~HasPtr();
    private:
    int i;
    string* ps;
    std::size_t* use;
    };
    //析构函数
    HasPtr::~HasPtr(){
    if (--*use == 0){
    delete ps;
    delete use;
    }
    }
    //拷贝运算符
    HasPtr& HasPtr::operator=(const HasPtr& rhs){
    ++*rhs.use;
    //先判断是否是最后一个拥有资源的类,如果是,则删除资源
    if (--*use){
    delete ps;
    delete use;
    }
    ps = rhs.ps;
    use = rhs.use;
    i = rhs.i;
    return *this;
    }

三、交换操作SWAP

  1. swap函数应该调用swap,而不是std::swap

    定义类自己的swap函数,如下:

     class HasPtr{
    public:
    friend void swap(HasPtr& lhs, HasPtr& rhs);
    };
    inline void swap(HasPtr& lhs, HasPtr& rhs){
    using std::swap;
    swap(lhs.ps, rhs.ps);
    swap(lhs.i, rhs.i);
    }

    每个swap函数都应该是未加限制的,这样如果存在类型特定的swap版本,则优先于特定的swap版本。

  2. 在赋值中使用swap

     HasPtr& HasPtr::operator=(HasPtr rhs){
    //参数是一个值,而不是引用
    swap(*this, rhs);
    return *this;
    }

四、对象移动

  1. 新标准中的一个特性是可以移动而非拷贝对象的能力。这样会大幅度提升性能。

  2. 为了支持移动操作,新标准引入了一种新的引用类型,右值引用,就是必须绑定到右值的引用。通过&&而不是&来获得引用。

    int &&rr = i * 42;

    int &&rr1 = 42; //正确:字面常量是右值

    int &&rr2 = rr1; //错误:变量是左值

  3. 标准move函数

    可以通过move显式地将右值引用绑定到一个左值上。

     int &&rr3 = std::move(rr1);
  4. 移动构造函数和移动赋值运算符

    移动构造函数的参数是该类类型的一个右值引用。

     StrVec::StrVec(StrVec &&s) noexcept     //移动操作不应抛出任何异常
    : elements(s.elements), first_free(s.first_free), cap(s.cap)
    {
    //令s进入这样的状态,对其运行析构函数是安全的
    s.elements = s.first_free = s.cap = nullptr; //置为nullptr后,便可析构rhs,不会影响
    } //移动赋值运算符
    StrVec& StrVec::operator=(StrVec&& rhs) noexcept{
    if (this != &rhs){
    free();
    elements = rhs.elements;
    first_free = rhs.first_free;
    cap = rhs.cap;
    rhs.elements = rhs.first_free = rhs.cap = nullptr;
    }
    return *this;
    }

### 学习《C++ Primer》- 6的更多相关文章

  1. 学习C++ Primer 的个人理解(一)

    <C++ Primer>这本书可以说是公认的学习C++最好的书,但我觉得不是特别适合作为教材,书中内容的顺序让人有些蛋疼.我个人认为初学此书是不能跳着看的.如果急于上手的话,我更推荐< ...

  2. 学习C++ Primer 的个人理解(九)

    这一章介绍顺序容器,在之前的第三章中,了解到的vector就属于顺序容器的一种. 一个容器就是一些特定类型对象的集合. 除了vector,还有哪些顺序容器? vector: 大小可变,随机访问的速度很 ...

  3. 学习C++ Primer 的个人理解(三)

    第三章,主要内容是字符串和数组.感觉作者的意图是希望读者可以早一点可以写出简单的小程序,并且可以早点接触迭代器这种思想. 在我看来,这种内容的难度并不大. 对于编程来说,最重要的应该是思想,类似vec ...

  4. 学习C++ Primer 的个人理解(二)

    本身就一定基础的读者我想变量常量这些概念应该已经不是问题了.但是本章还是有几个重点,需要特别留意一下的: 1.初始化和赋值是不同的操作 2.任何非0值都是true 3.使用新标准列表初始化,在有丢失精 ...

  5. 学习C++.Primer.Plus 11 使用类

    1.操作符重载 重载操作符的几个限制: a)         重载的至少有一个操作数是用户定义的类型,这将防止用户为标准类型重载操作符. b)         不能违反操作符原有来的句法规则. c)  ...

  6. 学习C++.Primer.Plus 10 对象和类

    1.类的声明和定义 类的声明和定义. 类声明的格式如下: class className { private://private 是类对象的默认访问控制,因此,可以省略 data member del ...

  7. 学习C++.Primer.Plus 8 函数探幽

    1. 内联函数 普通函数调用: 存储调用指令的地址->将函数参数复制到堆栈->跳到函数地址执行代码(返回值放到寄存器)->跳回调用指令处 2.  当代码执行时间很短,且会被大量调用的 ...

  8. 学习C++.Primer.Plus 7 函数

    C++的返回值类型不能是数组 函数原型中的变量名相当于点位符,因此不要求提供变量名. void cheers(int); C++中不指定参数列表时就使用活力号: void saybye(...); 通 ...

  9. 学习C++.Primer.Plus 6 分支语句和逻辑操作符

    ||. &&操作符是一个顺序点 < 操作符从左向右结合 ; < age < )//17<age为true, = 1,肯定 < 27.所以为整个条件为tru ...

  10. 学习C++.Primer.Plus 5 循环和关系表达式

    C++将赋值表达式的值定义为左侧成员的值 赋值操作符是自右向左结合的 cout.setf(ios:: boolalpha);//调用设置标记,命令cout输出true或false,而非1或0. 任何表 ...

随机推荐

  1. [iOS基础控件 - 4.2] APP列表 字典转模型Model

    A.使用字典加载数据的缺点 1.用户自行指定key,容易出错 2.存入.取出都需要key,容易混乱   B.模型 (MVC中的model) 1.字典与模型对比: (1)字典:存储数据,通过字符串类型的 ...

  2. BNUOJ 26475 Cookie Selection

    LINK:BNUOJ 26475 Cookie Selection 题意: 你在不停的输入数字a1,a2,a3,......,ak,当你输入#时,就把已输入数字中的第k/2+1删除,然后剩下的数字又组 ...

  3. MAC 终端 显示隐藏文件 关闭显示隐藏文件

    1.显示隐藏文件夹显示:defaults write com.apple.finder AppleShowAllFiles -bool true (1)复制“defaults write com.ap ...

  4. php 接收 Content-Type 是 application/json的请求数据

    工作中为其他公司编写了一个提供请求的接口,自己调试的时候是用form提交的,所以可以用$_POST取键接收方式,而对接联调的时候发现总是取不到数据,把$_POST整个序列化放入日志也是[] ,空的,于 ...

  5. 【转】简明vim练级攻略

    本文来自:http://coolshell.cn/articles/5426.html vim的学习曲线相当的大(参看各种文本编辑器的学习曲线),所以,如果你一开始看到的是一大堆VIM的命令分类,你一 ...

  6. 【11】在operator=中处理“自我赋值”

    1.自我赋值,看起来愚蠢,但是却合法.有些自我赋值一眼就可看出来.有些自我赋值是潜在的.比如:a[i] = a[j]; *px = *py; 甚至不同类型的指针,都指向同一个地址,也是自我赋值,这一类 ...

  7. nginx+tomcat动静分离的核心配置

    #所有jsp的页面均交由tomcat或resin处理 location ~ .(jsp|jspx|do)?$ { proxy_set_header Host $host; proxy_set_head ...

  8. MFC——AfxParseURL用法

    1.功能: 该函数解析URL字符串并返回服务的类型及组件,包含在 afxinet.h 头文件中. 2.定义 BOOL AFXAPI AfxParseURL(LPCTSTRpstrURL,DWORD&a ...

  9. File类的基本操作之读出所有目录路径

    package org.mark.file; import java.io.File; /** * File类的基本操作之读出所有文件夹路径 * 假设给定一个文件夹,要求将此文件夹中的所有文件都列出来 ...

  10. 5.6 太多分区引起OOM

    一个月之前,Scott和同事们发现公司有一个MySQL MHA集群的master(假设master机器名为hostA)每隔一周左右就会挂一次(指MySQL挂掉),在几周内,MHA来回切了好几次. 按照 ...