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

  • 一般C++传参方式有三种:
    • 对左值引用和右值引用分别重载,需要实现两个版本
    • 使用万能引用,使用时可能会实例化出多个版本,传参报错可读性差
    • 传值:
  • 可以考虑参数使用按值传递的情况:
    • 构造(拷贝构造或移动构造):对于可拷贝的,移动开销低的,并且总是会被拷贝的形参而言,按值传递和按引用传递的效率很接近,而且按值传递更容易实现,还可能会生成更少的目标代码,只是略微引入了一点性能开销
      • 按值传递的前提是移动操作的成本足够低廉,因为按值传递比按引用传递多一次移动操作
      • 只有当形参一定会被拷贝时,才考虑按值传递;比如有时函数开始先进行if检查,即使没有满足if条件而跳出函数,也会产生形参传值构造和析构的开销
    • 赋值(拷贝赋值或移动赋值)
      • 有时移动操作可能比直接拷贝开销更大:因为移动操作会涉及到原来对象的析构、新对象的构造,如果新对象比原来对象小,拷贝可以直接在原对象的位置上进行(具体见参考中的密码例子,如果新密码的长度比旧密码短,则新密码直接拷贝到旧密码的位置)
  • 参考

42:考虑置入而非插入

  • push_back可能会创建中间临时对象,但是emplace_back使用完美转发(直接将参数匹配到构造函数),不会创建临时对象

  • 多数场景下使用emplace_back比push_back高效

    • 要插入的值是通过构造函数插入容器,而非赋值
      • 赋值:比如emplace到容器begin,此时需要构造临时对象,然后将其移动到begin,此时emplace没有优势
    • 传入参数的类型和容器元素的类型不同
      • 如果传参类型和容器元素类型相同,也就不需要产生临时对象,emplace与push相同
    • 如果某个元素值添加重复,会使用新创建的元素值替换为原来旧的元素值
  • 使用注意:

    • 资源管理
      1
      2
      3
      
      std::list<std::shared_ptr<Widget>> ptrs;
      ptrs.push_back(std::shared_ptr<Widget>(new Widget, myDeleter)); // ok,不会发生内存泄露
      ptrs.emplace_back(new Widget, myDeleter); // 如果emplace_back内部、调用构造函数之前发生异常,则会发生内存泄露
      
      • 原因是延迟了资源管理对象的创建
      • 因此要么使用make_shared创建智能指针,要么先创建一个临时对象然后move到emplace中
    • 与显式构造函数的交互
  • 参考