拷贝控制-笔记

拷贝控制

  1. 拷贝控制操作定义:

    • 拷贝控制操作包括 拷贝构造函数,移动构造函数,拷贝赋值运算符,移动赋值运算符,析构函数。1 2决定了使用同一类型的对象来构造这个对象的时候会发生什么。3 4 决定了使用同一类型的对象来赋值给次对象时候会发生什么。5 决定了次对象销毁时候发生什么。
    • 拷贝控制操作的重要之处在于,即使我们不定义这几种操作,编译器也会替我们定义这些操作。但是这有时会导致灾难,因此我们一定要在定义一个类的时候自己定义这些操作。
  2. 拷贝构造函数

    • 如果一个构造函数的第一个参数是自身类型的引用, 并且没有其他参数,或者其他参数有默认值,那么就是拷贝构造函数。
      拷贝构造函数的参数必须是一个引用。如果不是引用,在调用拷贝构造函数时,首先需要将实参通过拷贝构造函数赋值给形参,那么就会一直循环。
    • 如果没有显式定义拷贝构造函数,那么编译器会为我们定义一个 合成拷贝构造函数
    • 初始化方法分为 直接初始化 和 拷贝初始化
      • 直接初始化就是 直接要求 编译器为其寻找合适的构造函数的初始化。
      • 拷贝初始化就是最终需要用到拷贝构造函数的初始化。是先建立一个对象(这个对象可以由构造函数构建而来,也可以是由类类型转换而来的),然后使用这个对象来初始化另外一个对象。典型的有使用 = 运算符进行的初始化,比如使用初始化器的。编译器调用拷贝构造函数或者移动构造函数,将右侧对象拷贝到构造对象中去。
        拷贝初始化是 不仅仅用于 = 的初始化,有时会隐式地调用:在将实参的赋值给形参(非引用)的时候,也会发生拷贝初始化。当返回值为一个非引用类型的时候,也会发生拷贝初始化。
    • 显式构造函数:前面使用explicit修饰的构造函数。有了这个修饰,这个构造函数只可以用于显式构造的方式,不可以用于隐式构造 的方式。所谓的隐式构造其实就是 在不使用这个构造函数形式的情况下,调用这个构造函数,将参数类型转换为这个对象类型,也叫做 类类型转换。隐式构造很普遍,比如使用 = 的拷贝初始化
      string s = "abc" 其实就是将"abc" 通过 隐式转换 为一个string类型临时对象,然后调用拷贝构造函数来初始化对象。
  3. 拷贝赋值运算符

    • 运算符的重载:某些运算符的重载必须定义为成员函数,也就是一个的操作数已经确定了。比如 = (赋值运算符)为二元运算符,应该定义在左侧类当中。参数就只有一个右侧操作数即可,因为左侧操作数就是this指针所指向的。
    • 如果不定义 拷贝赋值运算符的话,系统自动合成一个拷贝赋值运算符。
    • 在 = 赋值初始化的时候,使用的确实是 拷贝构造函数 或者 移动构造函数(当右侧为右值的时候)。
      在 = 进行赋值(不是初始化)的时候,使用拷贝赋值运算符
  4. 析构函数

    • 析构函数没有参数,不可以被重载,不过也可以进行重写,然后定义自己的退出策略。重写只会改变函数体,不会改变析构部分。
    • 析构函数分两部分,一部分是函数体,一部分是析构部分。析构部分是隐式的,析构部分按照类中成员出现的顺序的***逆序*** 进行析构。对于类成员,调用其析构函数,对于内置成员,直接释放空间。但是注意:对于内置指针,不会释放其所指向的空间,因此建议使用智能指针来替代内置指针
    • 调用析构函数的时机
      • 对于普通的类对象来说,当离开作用域的时候,会调用析构函数
      • 对于动态分配的对象,当使用delete运算符的时候,就会调用析构函数
      • 对于临时对象,常见他的完整表达式结束,就被销毁
    • 什么时候需要自定义析构函数?
      • 当数据成员中有指针指向的 动态内存 成员的时候,需要有自定义的析构函数。因为默认的析构函数不会执行delete操作,会造成内存泄漏。
      • 此时,不可以使用合成的拷贝构造函数和拷贝赋值运算符,因为会导致浅拷贝, 多个对象指向同一块内存区域,当销毁的时候,会delete多次,造成错误。
  5. 使用default 和 delete

    • 对于编译器可以自动生成的函数,我们在函数声明之后, ;之前,使用 = default 来要求编译器生成合成版本。
    • 对于有些函数,我们不定义的话,编译器自动生成。但是这样的函数是没有意义的,使用的话,是错误的。我们使用=delete 来进行删除。表示不希望使用这个函数。
    • delete有时也会由编译器来标注,因为某一个函数不符合运行条件。比如一个类的成员的析构函数为删除的或者不可访问的,那么这个类的合成析构函数也是删除的。
  6. 拷贝控制和资源管理

    • 类可以分为 行为像值 和 行为像指针。像值的可以在拷贝的时候,重新分配一个值,拷贝的两者之间(拷贝和被拷贝)是互不影响的。像指针的则是共享数据的。
    • 通常 资源管理 类更需要拷贝控制,
    • 在第13.2章中有一些例子讲解,讲类的设计。有需要的时候可以看看。
  7. 对象移动

    • 对象拷贝是很普遍的操作,但是在某些场景下,拷贝完之后就立即删除了,那就很不划算。使用移动就好很多。而有些类不共享,不可以拷贝,只可以移动。比如unique_ptr这种。因此,移动也是需要的。
      因此,很多类是不仅支持拷贝,而且支持移动。

    • 右值引用:

      • 表达式就是由 运算符和运算对象组成,可以进行计算,最后得到结果的一个式子。常量和变量就是最简单的表达式。
      • 左值和右值是表达式的属性。左值可以位于赋值语句的左侧,右值不可以。左值使用的是值的“身份”,也就是值在内存中的位置,在汇编中会将其替换为地址;右值是值的本身。左值一般是变量,而且是非const变量。其余的可以认为是右值。右值往往是用完就不再可以找得到的,因为其大多数时候不是变量(除了const),是短暂的。左值是长久的。
      • 表达式会要求左值或者右值,返回一个左值或者右值。
        比如: = 运算符要求左侧是一个左值,得到的结果也是一个左值。
      • 引用分为左值引用和右值引用。左值引用使用type &来标志,右值引用使用 type &&来标志。非const的左值引用只能引用一个左值。对于右值(常量等)可以使用const左值引用。
        而右值引用只可以引用右值,不可以直接引用左值。
        右值引用本身是一个左值。
      • move库函数可以将一个左值变成右值,从而完成右值引用的绑定。但是这个左值从此不可以再使用了。
    • 移动构造函数 和 移动赋值运算符

      • 移动构造函数的形参是一个 右值引用,因此要求实参是一个右值。参数和拷贝构造函数类似,只可以有一个右值引用形参,如果有其他的参数的话,需要有默认值。
        移动构造函数其实就是将资源进行一个 “抢夺”。右值原来的资源需要进行一些设置(比如将指针设置为nullptr),防止在调用析构函数的时候造成错误(对于临时对象来说,其语句结局就执行析构函数)。
      • 移动构造函数相比于 拷贝构造的函数的区别在于
        • 移动构造函数的调用者会接管实参的资源,不需要重新申请资源。在拷贝构造函数中,调用者可能还会申请资源
        • 实参的资源被完全掠夺,从此不可再用。而拷贝构造函数的实参没有变化
      • 移动构造函数和 移动赋值运算符 只是在没有 自定义的拷贝构造函数和拷贝赋值运算符,析构函数。并且所有的非static成员是可以移动的。(可以移动的元素包括内置类型,如果类类型可以移动,那么是可以移动的)
    • 如果没有 移动构造函数和移动赋值运算符,那么即使在参数为右值的情况下,也会调用拷贝构造函数。拷贝构造函数可以应对参数为右值的情况。但是如果有合成的或者定义的移动构造函数或者移动赋值运算符,那么当参数为右值的时候,会优先使用移动构造函数或者移动赋值运算符。

    • 右值引用和成员函数

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值