C++移动语义和std::move
2021年9月23日
字数统计:1108
C++ 11开始添加了移动语义,伴随着 std::move,对于现代C++有着极为深远的影响。
移动的思想是将对象的所有权进行传递,传统上基于指针的传递并不能满足所有需求:指针只能指向对象或者为空,而移动则是保留对象,将对象的内容设为空,这种方式使得RAII一定程度上脱离了原有的函数栈的粗糙控制,使对象本身的声明周期不改变的情况下,更灵活的利用内存。
移动构造和移动赋值
移动构造和移动赋值实际上原理非常简单:若对象是一个内置数据类型或者是由基本数据类型构成的一个类,代表着其所有内存都是在栈上申请的,此时移动构造,移动复制和普通构造,普通复制保持一致;如果对象中有数据存放在堆区,用指针进行管理,那么就将该指针交给新构造的对象,将旧对象的指针设置为空,其他必要的,在堆中申请内存的类型使用值复制。
之前的文章C++左值与右值 简单研究了左值与右值,而移动语义则是让一个左值具有右值的性质:在对某个对象进行移动的过程中,该对象将被视为右值,此时使用一个左值来接收这个右值,并在允许的情况下清空原对象,即完成了对源对象的移动。
具体的实现的代码如下:
#include <utility>
#include <iostream>
class A
{
public:
A ( int num ) // 构造
{
this -> num = new int ( num );
}
A ( A & x ) // 复制构造
{
this -> num = new int ( * x . num );
}
A ( A && x ) // 移动构造
{
this -> num = x . num ;
x . num = nullptr ;
}
void operator = ( A && x ) noexcept // 移动赋值
{
this -> num = x . num ;
x . num = nullptr ;
}
void operator = ( A & x ) // 普通(复制)赋值
{
this -> num = new int ( * x . num );
}
~ A () // 析构
{
delete ( this -> num );
}
private :
int * num ;
};
int main ()
{
A a { 10 };
A ab = std :: move ( a );
}
值得注意的是,移动构造函数的声明为 A(A &&x),其中 && 是右值引用的标记,换句话说,移动构造函数接收一个类型为A的右值引用。
std::move
使用移动语义不光需要在类中实现一个移动构造函数,还要使用 std::move 这个模板,在上面的代码中已经展示了 std::move 的用法。
std::move 的实现非常简单,实际上就只有一条语句:
template < class T >
constexpr std :: remove_reference_t < T >&&
move ( T && t ) noexcept ;
{
return static_cast < typename std :: remove_reference < T >:: type &&> ( t );
}
std::remove_ reference
std::remove_reference 的实现如下:
template < class T >
using remove_reference_t = typename remove_reference < T >:: type ;
template < class T >
struct remove_reference
{
using type = T ;
};
template < class T >
struct remove_reference < T &>
{
using type = T ;
};
template < class T >
struct remove_reference < T &&>
{
using type = T ;
};
std::remove_reference_t 是一个别名模板,用于简化模板的书写。
std::remove_reference 唯一的作用就是得到解除引用后的原本的数据的类型,换句话说,传入 int& 和 int&& 时返回 int,传入 int 时也返回 int。
回过头来看 std::move 实现中的 return static_cast<typename std::remove_reference<T>::type&&>(t);,你会发现这句话的意思是解除 t 的引用,获得其类型,并将其变为右值引用,然后用 static_cast 将 t 转换为 t 的类型的右值引用。
以上内容可由下面的代码验证:
#include <iostream>
#include <utility> // std::move, std::forward
#include <type_traits> // std::is_same, std::remove_reference
template < class T1 , class T2 >
void print_is_same ()
{
std :: cout << std :: is_same < T1 , T2 > () << std :: endl ;
}
int main ()
{
std :: cout << std :: boolalpha ;
print_is_same < int , int > ();
print_is_same < int , int &> ();
print_is_same < int , int &&> ();
print_is_same < int , std :: remove_reference < int >:: type > ();
print_is_same < int , std :: remove_reference < int &>:: type > ();
print_is_same < int , std :: remove_reference < int &&>:: type > ();
int a = 0 ;
int & c = a ;
int && b = std :: move ( a );
print_is_same < int && , decltype ( std :: move ( a )) > ();
print_is_same < int && , decltype ( std :: move ( b )) > ();
print_is_same < int && , decltype ( std :: move ( c )) > ();
}
如果一个类提供了移动构造或者移动赋值,并且在构造或者赋值时使用了std::move,那么static_ cast就会调用对应的具有移动语义的重载函数;如果一个类只能移动赋值,不能复制,那么static_ cast会直接选择移动赋值。
若无特殊声明,本人原创文章以
CC BY-SA 4.0许可协议
提供。
本站不欢迎非搜索引擎类,非个人学习类爬虫;严禁将文章直接爬取至其他站点。
若看到此条消息,说明你正在访问的网站可能是垃圾二手转载网站。
为了获得更好的浏览体验,请访问唯一原始网站:https://mysteriouspreserve.com/
本文永久链接:mysteriouspreserve.com/blog/2021/09/23/std-move/