针对可复制的形参,在移动成本低并且一定会被复制的前提下,考虑按值传递

  1. 要想函数针对左值实参实施复制,而针对右值实参实施移动,可以使用函数重载,形参分别为左值引用和右值引用,但是这样做就会出现两个函数,在工作量大的同时还可能会影响性能。
  2. 针对上述问题也可以使用万能引用,万能引用可以针对左值和右值产生不同的实例化结果,但是有些类型不能通过万能引用方式传递(比如大括号表达式、仅有声明的static const成员变量、位域等),如果传入了不正确的实参类型,编译器可能会报莫名其妙的错误。
  3. 当上述两种方法都不能work时,按值传递可能是个完全合理的策略,因为C++11中按值传递的形参仅在传入左值时才会被复制构造,传入右值时就会被移动构造。此时传入左值就会发生一次复制加一次移动,传入右值就会发生两次移动,与按引用相比,无论左值右值都存在一次额外的移动操作。
  4. 仅对于可复制的形参,才考虑按值传递,因为针对不可复制的形参首先会发生一次移动构造再发生一次移动赋值,因此成本是两次移动,比函数重载的方法翻了一倍(并且此时函数重载不会有接受左值引用的版本,由于形参是只移类型,因为复制左值总需要调用复制构造函数)。
  5. 按值传递仅在形参移动成本低廉的前提下才值得考虑,只有当移动操作成本低廉时一次移动带来的额外成本才可能是可以接受的。
  6. 当使用赋值来实施形参复制的话,当=右边占用内存更大时按值传递可能会带来额外的内存分配和回收成本,该成本可能会比移动操作的成本高出几个数量级,采用赋值方式复制形参的函数,其按值传递带来的额外成本取决于传入的类型、左值和右值实参的占比、类型是否使用动态分配内存,还有,在确实使用了动态分配内存的前提下该类型的赋值运算符如何实现以及与赋值目标相关联的内存尺寸相当的可能性高低,对于string类型还有取决于实现是否使用了小型字符串优化(SSO)。
  7. 按值传递的方式如果函数内部多次发生按值传递则整个函数调用链的成本可能会很高,并且不同于按引用传递,按值传递容易遭遇切片问题(当形参类型为基类,传入实惨为派生类时就会发生切片问题)。

考虑置入而非插入

  1. 容器中插入元素时可能会发生不止一次构造函数的开销:
     v.push_back(std::string("xyzzy"));
    

    (1)从字符串字面量出发构造string类型的临时右值变量temp
    (2)temp被传递给push_back的右值版本,绑定到形参,然后形参被插入到容器中,发生一次移动构造
    (3)随着push_back返回,temp被析构,调用一次string的析构函数

  2. emplace_back使用传入的任何实参在vector中构造一个string,不会涉及任何临时对象,emplace_back使用了完美转发所以只要没遇到完美转发的限制就可以通过emplace_back传递任意类型的任意数量和任意组合的实参。emplace_back可以用于任何支持push_back的容器,emplace_front和emplace也一样。
  3. 插入函数接受的是待插入对象,而置入函数接受的则是待插入对象的构造函数实参,这一区别就让置入函数得以避免临时对象的创建和析构,而插入函数则无法避免。
  4. 置入函数在以下几种情况下会比插入函数更高效:
    (1)欲添加的值是以构造而非赋值方式加入容器,向容器中添加值究竟是通过构造还是赋值,这一般取决于实现者
    (2)传递的实参类型与容器持有之物的类型不同,若类型相同就不需要构造和析构临时对象。
    (3)容器不太可能由于出现重复情况而拒绝待添加的新值,这意味着容器允许重复值或者所添加的大部分值都满足唯一性。
  5. 在调用持有资源管理对象的容器的插入函数时,由于会创建临时对象就算中途发生异常也能保证对象得以析构资源得以释放但是置入函数则不能保证资源被释放,因为发生异常时没有对象持有该资源。
  6. 采用=号的初始化语句称为复制初始化,采用括号或大括号的初始化语句称为直接初始化,复制初始化不允许调用带有explicit声明饰词的构造函数,而直接初始化就允许。置入函数使用的是直接初始化,能调用带有explicit饰词的构造函数,插入函数使用的是复制初始化,不能调用带有explicit饰词的构造函数。因此置入函数可能会执行在插入函数中会被拒绝的类型转换。