Item 23: Understand std::move and std::forward.

Item 23: Understand std::move and std::forward.

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

std::move 和 std::forward 并不像他们名字所表达的那样,实际上 std::move 并没有移动数据,std::forward 也并没有转发数据,并且它们在运行期什么也没做。

先说 std::move,我们看下它在 C++11 中简易的实现如下:

template<typename T>    // in namespace std
typename remove_reference<T>::type&&
move(T&& param)
{
  using ReturnType =                          // alias declaration;
	typename remove_reference<T>::type&&;  // see Item 9
  return static_cast<ReturnType>(param);
}

std::move 只是返回了右值引用。这里使用了 remove_reference 是为了去除引用标识符。当 T 是一个引用类型的时候,根据引用折叠原理,T&& 会被折叠成一个左值引用类型。所以 remove_reference 是为了去防止 T 是一个引用类型, 它会去除引用进而保证 std::move 返回一个右值引用。因此 std::move 只是做了类型转换,并没有移动数据。由于只有右值是可以被移动的,std::move 更像是说明经过它之后对象可能会被移动(可能,而不是一定,后文会有解释)。

而 C++14 的 std::move 更加简洁:

template<typename T>             // C++14; still in
decltype(auto) move(T&& param)   // namespace std
{
  using ReturnType = remove_reference_t<T>&&;
  return static_cast<ReturnType>(param);
}

std::move 的目的就是让编译器把修饰的变量看做是右值,进而就可以调用其移动构造函数。事实上,右值是仅可以被移动的对象,std::move 之后不一定一定调用构造函数。看下面的例子,假如你有这样的一个类:

class Annotation {
  public:
    explicit Annotation(std::string text) : text_(text) 
    std::string text_;
}

class Annotation {
 public:
  explicit Annotation(std::string text) : text_(std::move(text)) {} 
  std::string text_;
}; 

class Annotation {
 public:
  //这里换成了带有const
  explicit Annotation(const std::string text) : text_(std::move(text)) {}
  std::string text_;
}; 

第一个实现会发生两次拷贝,第二个实现会发生一次拷贝和一次移动,那么第三个实现会发生什么呢?

由于 Annotation 的构造函数传入的是一个 const std::string text,std::move(text) 会返回一个常量右值引用,也就是 const 属性被保留了下来。而 std::string 的 move 构造函数的参数只能是一个非 const 的右值引用,这里不能去调用 move 构造。只能调用 copy 构造,因为 copy 构造函数的参数是一个 const 引用,它是可以指向一个 const 右值。因此,第三个实现也是发生两次拷贝。

也可以用下面的例子验证一下:

#include <iostream>
#include <boost/type_index.hpp>

using boost::typeindex::type_id_with_cvr;

class A {
public:
  A(){
    std::cout << "constructon" << std::endl;
  }
  A(const A& a) {
    std::cout << "copy constructon" << std::endl;
  }
  A(A&& a) {
    std::cout << "move constructon" << std::endl;
  }
};

int main() {
  const A a1;
  std::cout << type_id_with_cvr<decltype(std::move(a1))>().pretty_name() << std::endl;
  auto a2(std::move(a1));
    
  return 0;
}

// output
constructon
A const&&
copy constructon

因此,我们可以总结出两点启示:

  • 第一,假如你想对象能够真正被移动,不要声明将其申明为 const,对 const 对象的移动操作会被转换成了拷贝操作。
  • 第二,std::move 不仅不移动任何东西,甚至不能保证被转换的对象可以被移动。唯一可以确认的是应用 std::move 的对象结果是个右值。

再说 std::forward。std::forward 也并没有转发数据,本质上只是做类型转换,与 std::move 不同的是,std::move 是将数据无条件的转换右值,而 std::forward 的转换是有条件的:当传入的是右值的时候将其转换为右值类型。

看一个 std::forward 的典型应用:

#include<iostream>
#include<chrono>

class Widget {
};

void process(const Widget& lvalArg) {
  std::cout << "process(const Widget& lvalArg)" << std::endl;
}

void process(Widget&& rvalArg) {
  std::cout << "process(Widget&& rvalArg)" << std::endl;
}

template<typename T>
void logAndProcess(T&& param) {
  auto now = std::chrono::system_clock::now();
  process(std::forward<T>(param));
}

int main () {
  Widget w;
  logAndProcess(w);              // call with lvalue
  logAndProcess(std::move(w));   // call with rvalue
}

// output
process(const Widget& lvalArg)
process(Widget&& rvalArg)

当我们通过左值去调用 logAndProcess 时,自然期望这个左值可以同样作为一个左值转移到 process 函数,当我们通过右值去调用 logAndProcess 时,我们期望这个右值可以同样作为一个右值转移到 process 函数。

但是,对于 logAndProcess 的参数 param,它是个左值(可以取地址)。在 logAndProcess 内部只会调用左值的 process 函数。为了避免这个问题,当且仅当传入的用来初始化 param 的实参是个右值,我们需要 std::forward 来把 param 转换成一个右值。至于 std::forward 是如何知道它的参数是通过一个右值来初始化的,将会在 Item 28 中会解释这个问题。

总结一下:

  • std::move 无条件将输入转化为右值。它本身并不移动任何东西。
  • std::forward 把其参数转换为右值,仅仅在参数被绑定到一个右值时。
  • std::move 和 std::forward 只是做类型转换,在运行时(runtime)不做任何事。
  • 8
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Data Mining: A Tutorial-Based Primer, Second Edition (Chapman & Hall/CRC Data Mining and Knowledge Discovery Series) by Richard J. Roiger 2016 | ISBN: 1498763979 | English | 529 pages | True PDF | 32 MB "Dr. Roiger does an excellent job of describing in step by step detail formulae involved in various data mining algorithms, along with illustrations. In addition, his tutorials in Weka software provide excellent grounding for students in comprehending the underpinnings of Machine Learning as applied to Data Mining. The inclusion of RapidMiner software tutorials and examples in the book is also a definite plus since it is one of the most popular Data Mining software platforms in use today." –Robert Hughes, Golden Gate University, San Francisco, CA, USA Data Mining: A Tutorial-Based Primer, Second Edition provides a comprehensive introduction to data mining with a focus on model building and testing, as well as on interpreting and validating results. The text guides students to understand how data mining can be employed to solve real problems and recognize whether a data mining solution is a feasible alternative for a specific problem. Fundamental data mining strategies, techniques, and evaluation methods are presented and implemented with the help of two well-known software tools. Several new topics have been added to the second edition including an introduction to Big Data and data analytics, ROC curves, Pareto lift charts, methods for handling large-sized, streaming and imbalanced data, support vector machines, and extended coverage of textual data mining. The second edition contains tutorials for attribute selection, dealing with imbalanced data, outlier analysis, time series analysis, mining textual data, and more. The text provides in-depth coverage of RapidMiner Studio and Weka’s Explorer interface. Both software tools are used for stepping students through the tutorials depicting the knowledge discovery process. This allows the reader maximum flexibility for their hands-on data mining experience.

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值