Chapter 5 右值引用、移动语义和完美转发
移动语义:用移动操作来替代昂贵的复制操作。
完美转发:函数模板转发其他函数,目标函数将接收到与转发函数所接受的完全相同的实参。
右值引用:让移动语义和完美转发成为可能。
实际情况:
- std::move其实并没有移动任何东西…
- 完美转发并不完美…
- 移动操作的成本并不一定比复制低…
- 移动语境中不一定能调到移动操作…
- "type&&"并不总是表示右值引用…
C++规则:形参总是左值,即使其型别是右值引用。
23 理解std::move和std::forward
std::move
std::move是无条件的将实参强制转换成右值的函数模板。
C++14中std::move的实现类似:
template<typename T>
decltype(auto) move(T&& param)
{
using ReturnType = remove_reference_t<T>&&;
return static_cast<ReturnType>(param);
}
移动失败的场景:下面的代码看似使用了移动,实则是复制,原因在于text具有const属性,因此代码不应该允许常量对象传递到有可能改动他们的函数中(比如移动构造函数)。
class Base {
public:
explicit Base(const std::string text) : value(std::move(text)){
}
private:
std::string value;
}
std::forward
std::forward是有条件的将实参强制转换成右值的函数模板。
典型场景如下,C++规则:形参总是左值 ,如果下面的代码中不加std::forward,则process拿到的参数一定是左值,而std::forward做的事情就是仅当用来初始化param的实参是个右值时,把param强制转换为右值型别。
void process(const string& lVal) {
cout << "left" << endl;
}
void process(string&& rVal) {
cout << "right" << endl;
}
template<typename T>
void function(T&& param) {
process(std::forward<T>(param));
}
int main() {
string s{"Hello."};
function(s);
function(std::move(s));
return 0;
}
二者的区别:
- std::move无条件的将实参强制转换成右值;
- std::forward仅在特定条件(传入的实参被绑定到右值)下执行强制转换。
为什么std::forward不能代替std::move?
- 方便:std::move只需要实参,std::forward需要实参和模板类型。
- 减少错误的可能:对于实参是左值引用时,用std::move转换完可顺利调用移动构造函数,而std::forward转换后因为没有变为右值,则调用复制构造函数,与初衷背离。
- 清晰:std::move的含义时无条件强制转换,std::forward的含义是仅对实参是右值引用时才强制转换。
要点速记
- std::move实施的是无条件的向右值型别的强制转换,就其本身而言,它不会执行移动操作。
- 仅当传入的实参被绑定到右值时,std::forward才针对该实参实施向右值性别的强制转换。
- 在运行期间,std::move和std::forward不会做任何操作。
24 区分万能引用和右值引用
万能引用:既可以是右值引用,又可以是左值引用;既可以绑定到const对象,又可以绑定非const对象;volatile对象和非volatile对象同理;甚至const和volatile同时修饰也同理。
万能引用需要具备两个条件:
- 必须是精确的T&&类型;
- 2.T的型别必须由推导而来。
template<typename T>
void func(std::vector<T>&& param);
void func(const T&& param);
auto&& var2 = var1;
template<typename T>
void func(T&& param);
void f(Base&& param);
Base&& var1 = Base();
template<typename T>
void func(std::vector<T>&& param);
要点速记
- 如果函数模板形参具备T&&型别,并且T的型别由推导而来,或如果对象使用auto&&声明其型别,则该形参或对象就是万能引用。
- 如果型别声明并不精确地具备T&&的形式,或者型别推导并未发生,则T&&就代表右值引用。
- 若采用右值来初始化万能引用,就会得到一个右值引用;若采用左值来初始化万能引用,就会得到一个左值引用。
|