c++ std::move 和 std::forward

  • 在C++11标准之前,C++中默认的传值类型均为Copy语义,即:不论是指针类型还是值类型,都将会在进行函数调用时被完整的复制一份。所以引入了move和forward
临时值(右值)简述
func("some temporary string"); // 尽管直接将一个常量传入函数中, C++还是大概率会创建一个string的复制
v.push_back(X()); // 初始化了一个临时X, 然后被复制进了vector
a = b + c; // b+c是一个临时值, 然后被赋值给了a
x++; // x++操作也有临时变量的产生(++x则不会产生)
a = b + c + d; //c+d是一个临时变量, b+(c+d)是另一个临时变量

vector<string> str_split(const string& s) {
  vector<string> v;
  // ...
  return v; // v是左值,但优先移动,不支持移动时仍可复制
}
使用 move
// Copy constructor
MyString(const MyString &str) {}
// Move constructor
MyString(MyString &&str) noexcept {}
// Copy assignment
MyString& operator=(const MyString& str) {}
// Move assignment
MyString& operator=(MyString&& str) {}

//使用std::move
void f_move(Object &&obj) {}
Object(Object &&object) noexcept: _str(std::move(object._str)) {}
  1. 实际上,C++中的move函数只是做了类型转换,并不会真正的实现值的移动,因此对于自定义的类来说,如果要实现真正意义上的 “移动”,还是要手动重载移动构造函数和移动复制函数。即:我们需要在自己的类中实现移动语义,避免深拷贝,充分利用右值引用和std::move的语言特性。
  2. 通常情况下C++编译器会默认在用户自定义的class和struct中生成移动语义函数。但前提是:用户没有主动定义该类的拷贝构造等函数!
  3. 如果我们没有提供移动构造函数,只提供了拷贝构造函数,std::move()会失效但是不会发生错误,因为编译器找不到移动构造函数就去寻找拷贝构造函数,这也是拷贝构造函数的参数是const T&常量左值引用的原因
  4. c++11中的所有容器都实现了move语义,move只是转移了资源的控制权,本质上是将左值强制转化为右值使用,以用于移动拷贝或赋值,避免对含有资源的对象发生无谓的拷贝
  5. move对于拥有如内存、文件句柄等资源的成员的对象有效,如果是一些基本类型,如int和char[10]数组等,如果使用move,仍会发生拷贝(因为没有对应的移动构造函数),所以说move对含有资源的对象说更有意义。
foward 向前的,前进的;

完美转发是指在函数模板中,完全依照模板的参数的类型,将参数传递给函数模板中调用的另 一个函数,即传入转发函数的是左值对象,目标函数就能获得左值对象,转发函数是右值对象, 目标函数就能获得右值对象,而不产生额外的开销。

  1. 什么是foward?

    1. 问题:使用move后,处理临时变量用右值引用T&&,处理普通变量用const引用const T&,需要分别建立两个函数,然后入参使用不同的类型,每个函数都要写两遍。
    2. 能不能将T &&类型和const T &类型合二为一呢?
  2. std::forward也被称为完美转发,即:保持原来的值属性不变:

    1. 如果原来的值是左值,经std::forward处理后该值还是左值。如果外面传来了左值,它就转发左值并且启用copy,同时它也还能保留const。
    2. 如果原来的值是右值,经std::forward处理后它还是右值。如果外面传来了右值临时变量,它就转发右值并且启用move语义。

这样一来,我们就可以使用forward函数对入参进行封装,从而保证了入参的统一性,从而可以实现一个方法处理两种类型!
正因为如此,forward函数被大量用在了入参值类型情况不确定的C++模板中!

template<typename T>
void f_forward(T &&t) {
 
 
    Object a = std::forward<T>(t);//调用了std::forward(t)来创建一个新的对象。
 
 
    std::cout << "forward this object, address: " << &a << std::endl;
}
 
 
int main() {
    Object obj{"abc"};

    //分别使用一个左值和一个右值调用了该模板函数。
    f_forward(obj);
    f_forward(Object("def"));
    return 0;
}


build this object, address: 000000CFAE8FFC78
copy this object, address: 000000CFAE8FFBD8
forward this object, address: 000000CFAE8FFBD8
destruct this object, address: 000000CFAE8FFBD8
build this object, address: 000000CFAE8FFCB8
move this object!
forward this object, address: 000000CFAE8FFBD8
destruct this object, address: 000000CFAE8FFBD8
destruct this object, address: 000000CFAE8FFCB8
destruct this object, address: 000000CFAE8FFC78

move和forward函数的区别
  1. 基本上forward可以cover所有的需要move的场景,毕竟forward函数左右值通吃
  2. 那为什么还要使用move呢?原因主要有两点:
    1. 首先,forward函数常用于模板函数这种入参情况不确定的场景中,在使用的时候必须要多带一个模板参数forward,代码略复杂
    2. 此外,明确只需要move临时值的情况下如果使用了forward,会导致代码意图不清晰,其他人看着理解起来比较费劲

你可能感兴趣的:(CPP,c++,java,开发语言)