Item 25: Use std::move on rvalue references, std::forward on universal references.

Item 25: Use std::move on rvalue references, std::forward on universal references.,第1张

Item 25: Use std::move on rvalue references, std::forward on universal references.

Effective Modern C++ Item 25 的学习和解读。


如果函数参数为右值引用,那么这个参数只能绑定到一个右值,你应该充分利用右值的特性(可以被移动),使用 std::move 无条件将参数转换为右值。


class Widget {
public:
  Widget(Widget&& rhs)        // rhs is rvalue reference
  : name(std::move(rhs.name)),
    p(std::move(rhs.p))
    {}private:
  std::string name;
  std::shared_ptr<SomeDataStructure> p;
};

万能引用既可以绑定到右值,也可以绑定到左值。


当万能引用的参数被初始为右值时候,应该使用 std::forward 将其转换为右值。


class Widget {
public:
  template<typename T>
  void setName(T&& newName)  // newName is universal reference
  { name = std::forward<T>(newName); }};

总的来说,在转发右值引用参数给其他函数时候,应该使用 std::move 无条件将其转为右值。


当转发万能引用参数给其他函数时候,应该使用 std::forward 有条件将其转换为右值,因为万能引用有可能绑定到右值。


虽然参数是右值引用时候,使用 std::forward 会将其转换为右值,但还是建议你使用 std::move,因为这样代码更加简洁,也更符合习惯。


如果参数是万能引用,则需要避免使用 std::move 转换为右值。


看下面的例子:

class Widget {
public:
  template<typename T>
  void setName(T&& newName) // universal reference
  { name = std::move(newName); } // compiles, but isprivate:
  std::string name;
  std::shared_ptr<SomeDataStructure> p;
};

std::string getWidgetName(); // factory function
Widget w;
auto n = getWidgetName(); // n is local variable
w.setName(n); // moves n into w!// n's value now unknown

这里使用 std::move 将会无条件将参数转为为右值,n 会被移动给 w.name,n 会变空,这显然不是好的代码设计。


为了让 setName 函数不修改入参,有人可能会想通过重载 setName 改善上面代码:

class Widget {
public:
  void setName(const std::string& newName)  // set from const lvalue
  { name = newName; }                  
  void setName(std::string&& newName)  // set from rvalue
  { name = std::move(newName); }};

这依然不是好的设计,还是有缺点。


一方面,上面的代码可能比较低效,考虑这样的调用:

w.setName("Adela Novak");

std::string 是可以直接通过字面字符串进行构造,如果是万能引用版本,则可以直接在 setName 内部通过字面字符串直接构造 w.name。


但是对于重载版本的 setName 来说,则会产生临时的 std::string 对象。


另一方面,最大的缺点是 setName 的参数若有 N 个的话,那需要写 2^N 个重载函数。


更糟糕的是,像模板函数不限制个数的参数时候,这种重载的方式更难以为继了。


template<class T, class... Args>            // from C++11
shared_ptr<T> make_shared(Args&&... args);  // Standard

template<class T, class... Args>            // from C++14
unique_ptr<T> make_unique(Args&&... args);  // Standard

需要注意的是,当我们在一个函数中使用 std::move 转换右值引用和 std::forward 转化万能引用时候,在这个参数最后一次使用时候才应用 std::move 或 std::forward 。


template<typename T>        // text is
void setSignText(T&& text)  // univ. reference
{
  sign.setText(text);  // use text, but don't modify it
  auto now = std::chrono::system_clock::now();  // get current time
  signHistory.add(now, std::forward<T>(text));  // conditionally cast text to rvalue
}

如果函数的入参是一个右值引用(或万能引用),函数体中返回这个入参(by value),你应该使用 std::move (std::forward) 来返回这个引用。


Matrix   // by-value return
operator+(Matrix&& lhs, const Matrix& rhs)
{
  lhs += rhs;
  return std::move(lhs);  // move lhs into return value
}

使用 std::move 将 lhs 转化为右值,可以促使编译使用移动而非拷贝的方式将 lhs 移动给函数返回值。


对于万能引用,情况也是类似的。


如果参数绑定到右值,使用 std::forward 可以促使编译器使用移动而非拷贝动作。


template<typename T>
Fraction   // by-value return
reduceAndCopy(T&& frac)   // universal reference param
{
  frac.reduce();
  return std::forward<T>(frac);  // move rvalue into return
}                                // value, copy lvalue

但是,上述的情况不能推广到函数中返回局部变量的场景。


看下面的例子:

Widget makeWidget()  // "Copying" version of makeWidget
{
  Widget w;  // local variable configure wreturn w;  // "copy" w into return value
}

你可能做如下 “优化” :

Widget makeWidget()  // Moving version of makeWidget
{
  Widget w;return std::move(w);  // move w into return value
}                       // (don't do this!)

“优化” 的版本反而会让编译器生成的代码效率更低,原因是因为编译器的返回值优化(RVO),可以查阅 C++ 返回值优化 RVO 了解更多,这里不再赘述了。


欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/langs/564772.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-04-06
下一篇 2022-04-06

发表评论

登录后才能评论

评论列表(0条)

保存