623 字
3 分钟
C++ 移动语义(补充)
2023-12-31

对之前移动语义的部分补充,主要是关于std::move的实现

std::move本质#

std::move本质上是把一个量(或一个值)强制转换成一个右值引用,然后再将该右值引用返回。但是在C++中存在引用折叠现象,具体是什么我们看下列例子。

引用折叠#

template <typename T>
T&& move(T &&param)
{
return static_cast<T &&>(param);
}
int main()
{
std::vector<int> v1{1, 2, 3};
auto v2 = move(v1);
std::cout << v1.size() << '\n';
}

在该move函数中,我们通过一个右值引用接受一个参数,再强制转换成T&&类型并返回,好像符合我们的想法,但实际上,move函数被推导为以下类型

std::vector<int> &move<std::vector<int> &>(std::vector<int> &param)

我们发现,返回值是一个引用,同时参数也是一个引用,即T被推导为std::vector<int> &类型,这不符合我们的预期。实际上这里就发生了引用折叠

我们传入的v1本身是一个左值,所以传入时应该为左值引用,由于三个&出现折叠,最终T&&整体为单个&类型,即左值引用。所以我们需要的应该是保留传入参数的基本类型(或者说没有任何引用的类型),防止出现引用折叠,并保证T只可能推导成为该基本类型。这里,在C++14以上的版本提供了std::remove_reference_t< >来实现。

template <typename T>
decltype(auto) move2(T &&param)
{
using ReturnType = typename std::remove_reference_t<T> &&;
// 如果T是 int && ,则返回 int ,得到 ReturnType = int &&
return static_cast<ReturnType>(param);
}

typename强调std::remove_reference_t<T>是一个类型,比如说intstd::string等,通过using定义新类型ReturnType为去掉所以引用后的T再加上&&组成的右值引用类型。假设Tint &&,那么std::remove_reference_t<T>就会得到int,再结合&&得到int &&,此时无论param是什么类型,都能被强制转换成一个右值引用。此处返回值类型使用decltype(auto),由于直接使用auto本质上和直接使用T做返回值一样,此时使用decltype进一步修饰可以保证保留原来的类型,之所以返回值不写ReturnType是因为它在函数内被声明定义,函数定义时还没有该类型,我们也可以像以下这样,可能看起来更明白做了什么。

template <typename T>
typename std::remove_reference_t<T> && move2(T &&param)
{
using ReturnType = typename std::remove_reference_t<T> &&;
// 如果T是 int && ,则返回 int ,得到 ReturnType = int &&
return static_cast<ReturnType>(param);
}
C++ 移动语义(补充)
https://laplace825.github.io/posts/cpp/cppmove_2/
作者
Laplace
发布于
2023-12-31
许可协议
CC BY-NC-SA 4.0