美文网首页
13-拷贝控制

13-拷贝控制

作者: 龙遁流 | 来源:发表于2016-12-26 15:15 被阅读0次

    13.1 拷贝,赋值与销毁

    以上这些操作,必须明白定义与不定义会对类的操作产生何种影响,变编译器定义的合成版本未必符合类设计的初衷。

    13.1.1 拷贝构造函数

    如果一个构造函数的第一个参数是同类型的引用,且任何其他参数都有默认值,则此构造函数为拷贝构造函数。

    必须是引用类型参数,因为在调用非引用参数的函数时,会拷贝实参,而拷贝实参又需要调用拷贝构造函数,那么会无休止的调用下去。

    Foo(const Foo&);

    合成拷贝构造函数会将其参数的成员(非static)逐个拷贝到正在创建的对象中。

    直接初始化:使用普通的函数匹配,选择参数最匹配的构造函数。

    拷贝初始化:将对象或者可以转换为相同类型的对象拷贝到正在创建的对象中。

    1,使用=运算符定义变量

    2,将对象作为实参传递给一个非引用类型

    3,从非引用类型返回类型的函数里返回一个对象

    4,使用初始值列表初始化一个数组中的元素或一个聚类中的成员

    13.1.2 拷贝赋值运算符

    Foo& operator=(const Foo&);

    如果运算符是一个成员函数,其左侧对象就绑定到隐式的this参数。

    合成拷贝赋值运算符:将右侧运算对象的每个成员(非static)赋予左侧运算对象的对应成员

    13.1.3 析构函数

    ~Foo();

    在一个构造函数中,成员的初始化是在函数体执行之前完成的,且按照在类中出现的顺序进行的;而在析构函数中,首先执行函数体,然后销毁成员,成员按初始化顺序逆序销毁。

    当一个对象被销毁时会自动调用其析构函数。当指向一个对象的引用会指针离开作用域是,析构函数不会执行。

    合成析构函数:空的函数体

    13.1.4 三/五法则

    如果一个类需要自定义析构函数(一般是销毁动态分配的内存),则可能也需要拷贝构造函数和拷贝赋值运算符。

    需要拷贝操作的类也需要赋值操作,反之亦然,但未必需要析构函数。

    13.1.5 使用=default

    使用=default修饰拷贝控制成员,编译器将生成相应成员的合成版本。=default在类内则隐式的声明为内联。

    Foo& operator=(const Foo&) = default;

    Foo(const Foo&) =default;

    只能用来修饰具有合成版本的成员函数。

    13.1.6 阻止拷贝

    将拷贝构造函数和拷贝赋值运算符定义为删除的函数来阻止拷贝,即,虽然声明了,但是不可以使用它们。

    Foo(const Foo&) = delete;//阻止拷贝

    Foo& operator=(const Foo&) = delete;//阻止赋值

    =delete必须出现在第一次声明的时候;可以对任何函数指定=delete

    对于删除了析构函数的类型,不可以定义此类型的变量或成员,但可以动态分配这种类型,但是无法释放它们。

    合成的拷贝控制成员可能是删除的

    如果类有数据成员不能被默认构造,拷贝,赋值,复制或销毁(delete或者private修饰),则此类对应的合成成员函数被定义为删除的。(引用成员或无法默认构造的const成员)

    通过声明但不定义private的拷贝构造函数或拷贝赋值运算符,试图拷贝或赋值的操作在编译阶段被标记为错误;成员函数或友元函数中的拷贝或赋值会导致链接错误。(旧的方法)

    13.2 拷贝控制和资源管理

    管理类外的类必须定义拷贝控制成员。

    13.2.1 行为像值的类

    对于类管理的资源,每个对象都有一份拷贝。

    赋值操作会销毁左侧运算对象的资源,之后从右侧运算对象拷贝数据,必须确保自赋值是异常安全的(可先将右侧运算对象的数据拷贝到零时对象中)。

    13.2.2 行为像指针的类

    多个此类的对象共享同一份数据(使用智能指针或引用计数管理)

    13.3 交换操作

    void swap(Foo &lhs, Foo &rhs){

    using std::swap;//若没有类型自定义的swap版本,则调用std的版本

    swap(lhs.h, rhs.h);//类型自定义的版本

    }

    拷贝并交换技术

    Foo& Foo::operator=(Foo rhs){

    swap(*this, rhs);

    return *this;

    }

    参数并不是引用,在函数执行完后会释放。可自动处理自赋值的情况,且是异常安全的。

    13.6 对象移动

    当一个对象拷贝后就立即销毁,此时使用移动操作可以提高性能。

    标准库容器,string,shared_ptr类支持移动和拷贝,IO类和unique_ptr类只支持移动。

    13.6.1 右值引用

    即,必须绑定到右值的引用。只能绑定到将要销毁的对象,故可以将右值引用的资源移动到另一个对象中。

    一个左值表达式表示一个对象的身份,而右值表达式表示的是对象的值。

    int &&r = i*42;

    返回左值引用的函数,连同赋值,下标,解引用和前置递增递减运算符,都返回左值;

    返回非引用类型的函数,连同算数,关系,位以及后置递增递减运算符,都生成右值,可以用const的左值引用和右值引用绑定。

    左值有持久的状态,右值要么是字面值常量,要么是表达式求值过程中创建的临时变量。

    右值意味着:该对象将被销毁;该对象没有其他用户。故使用右值引用的代码可以自由的接管引用的对象的资源。

    int &&rr1 = 42;

    int &&rr2 = rr1;//错误:rr1是右值引用类型的变量,是左值

    #include <utility>

    int &&rr3  =std::move(rr1);//move函数获得绑定到左值上的右值引用。

    move意味着希望像处理右值一样处理一个左值,即,除了对rr1赋值和销毁外,代码不会再使用它。

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

    从给定对象“窃取”而不是拷贝资源。

    移动拷贝构造函数第一个参数是该类类型的右值引用,其他的参数必须都具有默认实参。

    StrVec::StrVec(StrVec &&s) noexcept:e(s.e), f(s.f){s.e = s.f = nullptr;}

    1,移动操作不应该抛出异常,noexcept通知标准库移动操作是安全的的,无需标准库做额外的操作

    2,成员初始化器中接管s中的资源

    3,函数体中使对s进行析构是安全的

    noexcept在声明和定义中都需要指定。

    StrVec &StrVec::operator=(StrVec &&rhs) noexcept{}

    在移动操作之后,移后源对象必须保持有效的,可析构的状态,但用户不可对其值进行任何假设。

    当一个类没有定义任何自己版本的拷贝控制成员,且类的每个非static数据成员都可以移动时,编译器才会合成移动构造函数或移动赋值运算符。

    可以移动:内置类型,类类型定义了相应的移动操作。

    移动操作不会隐式的定义为删除的函数,但显式定义=default的移动操作,且编译器不能移动所有成员,则编译器将移动操作定义为删除的函数。

    如果类定义了一个移动构造函数或一个移动赋值运算符,则该类的合成拷贝构造函数和拷贝赋值运算符会被定义为删除的。

    如果一个类既有移动构造函数,又有拷贝构造函数,则使用普通的函数匹配规则来选择调用。如果没有移动构造函数或移动赋值运算符,右值会被拷贝。

    移动迭代器

    移动迭代器的解引用运算符生成一个右值引用,make_move_iterator(origin_iterator);函数将普通的迭代器转换为一个移动迭代器,会调用相应的西东构造函数或移动赋值运算符操作。

    13.6.3 右值引用和成员函数

    成员函数提供移动版本:

    void push_back(const X&);//拷贝

    void push_back(X&&);//移动

    引用限定符

    Foo &operator=(const Foo&) &;//只能向可修改的左值赋值

    引用限定符可以是&和&&,分别指出this可以指向一个左值或右值,只能用于非static的成员函数,且必须同时出现在声明和定义中。

    一个函数可以同时用const和引用限定,但引用限定符必须跟随在const之后。

    引用限定符可以区分重载版本,并且可以和const综合起来区分。

    当定义多个具有相同名字和相同参数列表的成员函数时,必须所有函数都加上引用限定符或都不加。

    相关文章

      网友评论

          本文标题:13-拷贝控制

          本文链接:https://www.haomeiwen.com/subject/okyjvttx.html