- 在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)) {}
- 实际上,C++中的move函数
只是做了类型转换
,并不会真正的实现值的移动,因此对于自定义的类来说,如果要实现真正意义上的 “移动”,还是要手动重载移动构造函数和移动复制函数
。即:我们需要在自己的类中实现移动语义,避免深拷贝,充分利用右值引用和std::move的语言特性。 - 通常情况下C++
编译器会默认在用户自定义的class和struct中生成移动语义函数
。但前提是:用户没有主动定义该类的拷贝构造等函数! - 如果我们
没有提供移动构造函数,只提供了拷贝构造函数,std::move()会失效但是不会发生错误
,因为编译器找不到移动构造函数就去寻找拷贝构造函数
,这也是拷贝构造函数的参数是const T&常量左值引用的原因 - c++11中的所有容器都实现了move语义,
move只是转移了资源的控制权
,本质上是将左值强制转化为右值使用,以用于移动拷贝或赋值,避免对含有资源的对象发生无谓的拷贝
- move对于拥有如内存、文件句柄等资源的成员的对象有效,如果是一些基本类型,如int和char[10]数组等,如果使用move,仍会发生拷贝(因为没有对应的移动构造函数),所以说move对含有资源的对象说更有意义。
foward 向前的,前进的;
完美转发是指在函数模板中,完全依照模板的参数的类型,将参数传递给函数模板中调用的另 一个函数,即传入转发函数的是左值对象,目标函数就能获得左值对象,转发函数是右值对象, 目标函数就能获得右值对象,而不产生额外的开销。
-
什么是foward?
- 问题:使用move后,处理临时变量用右值引用T&&,处理普通变量用const引用const T&,需要分别建立两个函数,然后入参使用不同的类型,每个函数都要写两遍。
- 能不能将T &&类型和const T &类型合二为一呢?
-
std::forward也被称为完美转发,即:保持原来的值属性不变:
- 如果原来的值是左值,经std::forward处理后该值还是左值。如果外面传来了左值,它就转发左值并且启用copy,同时它也还能保留const。
- 如果原来的值是右值,经std::forward处理后它还是右值。如果外面传来了右值临时变量,它就转发右值并且启用move语义。
这样一来,我们就可以使用forward函数对入参进行封装,从而保证了入参的统一性,从而可以实现一个方法处理两种类型!
正因为如此,forward函数被大量用在了入参值类型情况不确定的C++模板中!
template<typename T>
void f_forward(T &&t) {
Object a = std::forward<T>(t);//调用了std::forward<T>(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函数的区别
- 基本上forward可以cover所有的需要move的场景,毕竟forward函数左右值通吃
- 那为什么还要使用move呢?原因主要有两点:
- 首先,forward函数常用于模板函数这种入参情况不确定的场景中,在使用的时候必须要多带一个模板参数forward,代码略复杂
- 此外,明确只需要move临时值的情况下如果使用了forward,会导致代码意图不清晰,其他人看着理解起来比较费劲