1. copy控制涉及类的五个成员函数:,这五个成员函数被显式或隐式的被调用,各司其职:
  2. 我们必须根据类的实际情况来确定是否需要显式定义这些成员函数:
  3. 什么是拷贝构造函数呢?简单讲就是该函数的第一个形参是reference to const的类类型,如果有其他形参的话,必须有默认实参:,至于为什么第一个形参是reference to const有两点原因:reference是杜绝递归调用,const是希望防止修改被拷贝的对象以及接收const对象。
  4. 拷贝构造函数发生的地方有很多,例如形参初始化、非引用的返回类型等等,所以我们也没有显式的使用explicit,值得注意,拷贝初始化不一定就是调用copy constructor。也有可能调用move constructor:
  5. 拷贝构造函数被用于初始化非引用类型的形参解释了为什么拷贝构造函数本身需要第一个参数为引用类型的对象:
  6. 编译器可能会优化copy/move constructor:,我们可以使用g++的选项来禁用优化,注意C++版本建议为11:g++ -fno-elide-constructors -std=c++11 -Wall -o main main.cc
  7. 什么是overload operators?简单讲,它是一个名为operator?,同时形参为操作数的函数:
  8. 编译器也会自动生成一个合成的拷贝赋值操作符:
  9. 析构函数做了构造函数相反的事情,释放资源:,因为它没有形参,所以它不可以被重载。具体而言,它也想构造函数分成两个部分:function bodydestruction部分:
  10. 析构函数就是做了构造函数相反的事情,就是类的设计者希望在最后一次使用对象后进行的
    一系列善后操作。析构函数有两部分组成:函数体 + 隐式析构成员阶段。当我们没有显式定义析构时,编译器就会为我们定义合成的析构函数。除了我们手动申请的动态内存需要我们手动释放外,其他对象的析构函数是被自动调用的 :,具体而言:
  11. 一个经验法则是,如果你需要显式定义析构函数的话,那么也可能需要显式定义copy constructor/assignment:,否则可能会使得类内的成员指针指向同一个动态分配的对象。后果就是充满bug!
  12. 如果需要copy constructor,那么也极有可能需要copy assignment:。但是我们可能不需要显式的析构函数,不过这种需求较为奇葩:
  13. 我们可以显式要求编译器为我们生成合成的控制成员函数:
  14. 对于不支持拷贝的类而言(stream),我们应该禁用copy constructor/assignment。在新标准前,我们需要显式声明无定义+private以防止编译器自动生成。新标准支持我们用简便的方式做到这一点= delete::,但是析构函数不应该被删除:,否则就无法定义该类的对象。
  15. 拷贝控制成员可能被编译器合成为=delete:,宗旨就是为了自圆其说,安全。
  16. 类的copy operations决定该类像value还是pointer,当然如果你禁止copy,那就都不是:
  17. XX- assignment = destructor(释放左操作数已有的空间) + XX-constructor(拷贝右操作数),我们需要合理的顺序保证正确进行,尤其是self- assignment:
  18. 像pointer那样共享潜在的数据时,没有我们想的简单。我们需要像计数指针一样保证对象安全:,所以计数器就很有必要:,但是我们不能简单地将它作为类成员,而是将其作为类外的动态内存对象:
  19. 我们为类自定义swap函数时,就可以让库中的算法调用该函数,而不是库版本的swap,它开销更大一些:,对于库版本swap的可能实现:
  20. swap函数用于assignment的swap-and-copy技术:
  21. 如果能避免copy,那么尽量使用move:,目前来看,move constructor在合适条件下(右值引用),会被自动地调用。如果我们想要强制手动调用,可以使用std::move(),move操作保证所有权转移后,原来的对象处于可以被销毁的安全状态(也是为什么move constructor/assignment不是const形参的原因):,看例子:,以及实际性能:
  22. 首先有很多环境下会发生copy,但这些里面有一些环境中copy没有性价比,例如copy后,原来的对象会被立即释放掉,此外有一些对象无法copy,例如stream、unique_ptr等。新标准下,我们鼓励move以加快速度:
  23. 为了配合move,新标准引入右值引用类型。它是一个必须被绑定到右值的引用类型,换句话说就是只能被绑定到即将被摧毁的对象上。从而方便我们将其资源move到其他对象:,左值是持久的对象本身,而右值是临时的昙花一现:,这里注意lvalue reference to constrvalue reference
  24. 左值/右值都是表达式的属性罢了,因为一个变量是一个只有操作数没有操作符的表达式,所以它也有左值/右值的属性,不巧的是,Variable expressions are lvalues.:,材料建议联系变量是持久,而右值是短暂的进行理解。
  25. 我们可以使用std::move()操作将某个左值对象转换成右值:,注意,被move对象的value可能会发生变化。此外我们使用std::move()是为了防止命名冲突。
  26. 为了让自定义的类受益于move而非copy,我们需要定义move constructormove assignment:,里面的noexpect关键字是显式声明该函数不会抛出异常,从而减少系统对异常的检查:,以让系统放心使用:
  27. 注意move是新标准推出代替copy出现的某些场景的,例如copy即将被摧毁的对象的场景下,我们希望编译器能使用move避免copy,所以move操作并不总是被自动生成,它是稀罕物:
  28. 如果一个类同时有copy和move版本,我们根据普通的函数匹配来确定调用:,通常精确匹配优先,如果没有move版本,那么就会强制转换rvalue以调用copy版本:
  29. 如果一个类有copy和move的构造函数,同时用swap-and-copy实现的赋值运算符的话,那么该运算符会起到copy和move assignment的效果,但是它们和分开定义有有很大的性能差别:
  30. 五个copy control成员看成一个单元,如果你需要显式的析构,那么大概率需要copy constructor/assignment,最后move constructor/assignment是锦上添花:
  31. 少在用户代码使用move:
  32. 其他的成员函数也可以就move和copy进行函数重载:,例如:
  33. 我们可以使用引用限定符来限定左操作数是左值:,,对于引用限定,要不全有,要么全无,不能造成歧义: