C++ Primer 第13章 知识点回顾

来源:互联网 发布:mac xampp设置htdoc 编辑:程序博客网 时间:2024/06/05 10:52

13.1.1 拷贝构造函数

  1. 直接初始化:根据参数选择最匹配的构造函数
  2. 拷贝初始化:使用拷贝构造函数或移动构造函数来完成,要求编译器将右侧运算对象拷贝到正在创建的对象中,如果需要的话还要进行类型转换
  3. Foo(const Foo&);//声明拷贝构造函数
  4. 有explicit构造函数的对象只能使用直接初始化
  5. 如果我们没有为一个类定义拷贝构造函数,编译器会为我们定义一个。即使我们定义了其他构造函数,编译器也会为我们合成一个拷贝构造函数(问题?我们定义了一个拷贝构造函数,编译器还会合成吗?)

13.1.2 拷贝赋值运算符

  1. Foo& operator=(const Foo&);

11.1.3 析构函数

  1. ~Foo();
  2. 没有参数,不能被重载,因此一个类只有一个析构函数
  3. 在析构函数中,首先执行函数体,然后销毁成员。成员按初始化顺序的逆序销毁。析构部分是隐式的。成员销毁时发生什么完全依赖于成员的类型。销毁类类型的成员需要执行成员自己的析构函数。内置类型没有析构函数,因此销毁内置类型成员什么也不需要做。
  4. 隐式销毁一个内置指针类型的成员不会delete他所指向的对象
  5. 当指向一个对象的引用或指针离开作用域时,析构函数不会执行

我们只能对具有合成版本的成员函数使用=default,即,默认构造函数或拷贝控制成员


13.1.6 阻止拷贝

  1. =delete必须出现在函数第一次声明的时候
  2. 本质上,当不可能拷贝、赋值、销毁类的成员时,类的合成拷贝控制成员就被定义为删除的
  3. 通过声明但不定义private的拷贝构造函数(这是合法的),我们可以预先阻止任何拷贝该类型对象的企图

13.2 拷贝控制和资源管理

  1. 通常,管理类外资源的类必须定义拷贝控制成员
  2. 行为像指针的类:采用引用计数。一种方法是将计数器保存在动态内存中(这样这个类的多个相关对象可以共享这个计数器)

13.3 交换操作

  1. class HasPtr
    {
    friend void swap(HasPtr&, HasPtr&);
    //其他成员。。。
    };
    inline
    void swap(HasPtr &lhs, HasPtr &rhs)
    {
    using std::swap;//必须保证std的swap在当前作用域可见,std的swap直接交换两个成员(temp=a;a=b;b=temp)
    swap(lhs.ps, rhs.ps);//如果有类特定版本的swap,会调用特定版本的swap,因为其匹配结果优于std版本。反之会调用std的swap
    swap(lhs.i, rhs.i);
    }
  2. HasPtr& operator=(HasPtr rhs)
    {
    swap(*this, rhs);
    return *this;
    }

    使用拷贝和交换的赋值运算符是异常安全的,且能正确处理自赋值
  3. 自己提供swap是为了避免std::swap交换对象时不必要的内存分配,提高效率,通常直接交换指针值

13.6.1 右值引用

  1. 一般而言,一个左值表达式表示的是一个对象的身份,而一个右值表达式表示的是对象的值
  2. 不能将右值引用绑定到左值
  3. int &&rr=42;
  4. 变量都是左值。不能将一个右值引用绑定到一个右值引用类型的表达式上
  5. int &&rr=std::move(rr1);//我们可以销毁一个移后源的对象,也可以赋予它新值,但是不能使用一个移后源对象的值。使用move的代码应该使用std::move而不是move,以避免潜在的名字冲突

13.6.2 移动构造函数和移动赋值运算符

  1. StrVec::StrVec(StrVec&& s) noexcept:/*成员初始化器*/ {//构造函数体}我们必须在类头文件的声明和定义中都指定noexcept
  2. 不加noexcept,会使用拷贝构造函数而不是移动构造函数。
  3. 合成的移动操作:如果一个类定义了自己的拷贝构造函数、拷贝赋值运算符或者析构函数,编译器就不会为他合成移动构造函数和移动赋值运算符了。只有当一个类没有定义任何自己版本的拷贝控制成员,且它的所有数据成员都能移动构造或移动赋值时,编译器才会为他合成移动构造函数或移动赋值运算符。
  4. 定义了一个移动构造函数或移动赋值运算符的类必须也定义自己的拷贝操作,否则这些成员默认地(合成的版本)被定义为删除的
  5. 用拷贝构造函数代替移动狗仔函数几乎肯定是安全的
  6. 拷贝并交换赋值运算符可以同时实现拷贝赋值运算符和移动赋值运算符两种功能(前提是此类定义了一个移动构造函数,对右值,函数传值参数执行移动构造,对左值,执行拷贝构造)
    7.

三五法则:如前所述,某些类必须定义拷贝构造函数,拷贝赋值运算符和析构函数才能正确工作。这些类通常拥有一个资源,而拷贝成员必须拷贝此资源。一般来说,拷贝一个资源会导致一些额外开销。在这种拷贝并非必要的情况下,定义了移动构造函数和移动赋值运算符的类就可以避免此问题。

  1. 移动迭代器:解引用生成一个右值引用。通过调用标准库的make_move_iterator函数将普通迭代器转换为移动迭代器make_move_iterator(vec.begin());

13.6.3 右值引用和成员函数

  1. void push_back(const X&);//拷贝:绑定到任意类型的X
      void push_back(X&&);//移动:只能绑定到类型X的可修改的右值

      
  2. P482页 讲得非常好!
  3. 新标准库类仍然循序想右值赋值,但是,我们可能忘在自己的类中阻止这种做法。我们希望强制左侧运算对象(即this指向的对象)是一个左值。我们指出this的左右值属性的方式与定义const成员函数相同,即,在参数列表后放置一个引用限定符。Foo &operator=(const Foo&) &;//只能向可修改的左值赋值,引用限定符可以是& 或&&。
  4. Foo anotherMem() const &;//注意const和&的次序
  5. 引用限定符可以区分重载
  6. 如果一个成员函数有引用限定符,则具有相同参数列表的所有版本都必须有引用限定符
0 0