C++11 std::move 强制转换为右值

【1】std::move

在C++11中,标准库在<utility>中提供了一个有用的函数std::move。

这个函数的名字很具有迷惑性,因为实际上std::move并不能移动任何东西,它唯一的功能:将一个左值强制转化为右值引用,继而可以通过右值引用使用该值,以用于移动语义。

从实现上讲,std::move基本等同于一个类型转换:

static_cast<T&&>(lvalue);

【2】应用注意项

(1)被std::move转化的左值,其生命期并没有随着转化而改变。

请看这个典型误用std::move的示例:

 1 #include <iostream>
 2 using namespace std;
 3
 4 class Moveable
 5 {
 6 public:
 7     Moveable(): i(new int(3)) {}
 8     ~Moveable() { delete i; }
 9     Moveable(const Moveable & m): i(new int(*m.i)) { }
10     Moveable(Moveable&& m) : i(m.i)
11     {
12         m.i = nullptr;
13     }
14
15     int* i;
16 };
17
18 int main()
19 {
20     Moveable a;
21     Moveable c(move(a));   // 会调用移动构造函数
22     cout << *a.i << endl;  // 运行时错误
23 }

显然,为类型Moveable定义了移动构造函数。

这个函数定义本身没有什么问题,但调用的时候,使用了Moveable c(move(a));这样的语句。

这里的a本来是一个左值变量,通过std::move将其转换为右值。

这样一来,a.i就被c的移动构造函数设置为指针空值。

由于a的生命期实际要到main函数结束才结束,那么随后对表达式*a.i进行计算的时候,就会发生严重的运行时错误。

当然,标准库提供该函数的目的不是为了让程序员搬起石头砸自己的脚。

事实上,要使用该函数,必须是程序员清楚需要转换的时候。

比如上例中,程序员应该知道被转化为右值的a不可以再使用。

(2)通常情况下,需要转换成为右值引用的还确实是一个生命期即将结束的对象。

比如下例:

 1 #include <iostream>
 2 using namespace std;
 3
 4 class HugeMem
 5 {
 6 public:
 7     HugeMem(int size) : sz(size > 0 ? size : 1)
 8     {
 9         c = new int[sz];
10     }
11     ~HugeMem()
12     {
13         delete []c;
14     }
15     HugeMem(HugeMem && hm) : sz(hm.sz), c(hm.c)
16     {
17         hm.c = nullptr;
18     }
19
20     int* c;
21     int sz;
22 };
23
24 class Moveable
25 {
26 public:
27     Moveable() : i(new int(3)), h(1024) { }
28     ~Moveable() { delete i; }
29     Moveable(Moveable && m) : i(m.i), h(move(m.h)) // 强制转为右值,以调用移动构造函数
30     {
31         m.i = nullptr;
32     }
33
34     int* i;
35     HugeMem h;
36 };
37
38 Moveable GetTemp()
39 {
40     Moveable tmp = Moveable();
41     cout << hex << "Huge Mem from " << __func__ << " @" << tmp. h. c << endl; // Huge Mem from GetTemp @0x0086E490
42     return tmp;
43 }
44
45 int main()
46 {
47     Moveable a(GetTemp());
48     cout << hex << "Huge Mem from " << __func__ << " @" << a. h. c << endl; // Huge Mem from main @0x0086E490
49 }

定义了两个类型:HugeMem和Moveable,其中Moveable包含了一个HugeMem的对象。

在Moveable的移动构造函数中,我们就看到了std::move函数的使用。

该函数将m.h强制转化为右值,以迫使Moveable中的h能够实现移动构造。

这里可以使用std::move,是因为m.h是m的成员,既然m将在表达式结束后被析构,其成员也自然会被析构,因此不存在生存期不合理的问题。

关于std::move使用的必要性问题,在这里再赘述(可参见随笔《移动语义》)一遍:

如果不使用std::move(m.h)这样的表达式,而是直接使用m.h这个表达式,由于m.h是个左值,就会导致调用HugeMem的拷贝构造函数来构造Moveable的成员h。

如果是这样,移动语义就没有能够成功地向类的成员传递。换言之,还是会由于拷贝而导致一定的性能上的损失。

(3)如何判断一个类型是否具有可移动构造函数?

在标准库的头文件<type_traits>里,我们还可以通过一些辅助的模板类来判断一个类型是否是可以移动的。比如:

is_move_constructible、

is_trivially_move_constructible、

is_nothrow_move_constructible,使用方法仍然是使用其成员value。示例代码:

 1 #include <iostream>
 2 #include <type_traits>
 3 using namespace std;
 4
 5 class HugeMem
 6 {
 7 public:
 8     HugeMem(int size) : sz(size > 0 ? size : 1)
 9     {
10         c = new int[sz];
11     }
12     ~HugeMem()
13     {
14         delete [] c;
15     }
16     HugeMem(HugeMem && hm) : sz(hm.sz), c(hm.c)
17     {
18         hm.c = nullptr;
19     }
20
21     int* c;
22     int sz;
23 };
24
25 class Moveable
26 {
27 public:
28     Moveable() : i(new int(3)), h(1024) { }
29     ~Moveable() { delete i; }
30     Moveable(Moveable && m) noexcept : i(m.i), h(move(m.h))  // 强制转为右值,以调用移动构造函数
31     {
32         m.i = nullptr;
33     }
34
35     int* i;
36     HugeMem h;
37 };
38
39 int main()
40 {
41     cout << is_move_constructible<HugeMem>::value << endl;             // 1 测试类型是否具有移动构造函数
42     cout << is_move_constructible<Moveable>::value << endl;            // 1
43     cout << is_trivially_move_constructible<HugeMem>::value << endl;   // 0 测试类型是否具有普通移动构造函数
44     cout << is_trivially_move_constructible<Moveable>::value << endl;  // 0
45     cout << is_nothrow_move_constructible<HugeMem>::value << endl;     // 0 测试类型是否具有nothrow移动构造函数
46     cout << is_nothrow_move_constructible<Moveable>::value << endl;    // 1
47 }

可以判断是否具有移动构造函数、是否具有普通移动构造函数、是否具有不抛异常的移动构造函数。

(4)移动语义对泛型编程的积极意义

一个比较典型的应用是可以实现高性能的置换(swap)函数。

如下代码:

template <class T>
void swap(T& a, T& b)
{
    T tmp(move(a));
    a = move(b);
    b = move(tmp);
}

如果T是可以移动的,那么移动构造和移动赋值将会被用于这个置换。

代码中,a先将自己的资源交给tmp,随后b再将资源交给a,tmp随后又将从a中得到的资源交给b,从而完成了一个置换动作。

整个过程,代码都只会按照移动语义进行指针交换,不会有资源的释放与申请。

而如果T不可移动却是可拷贝的,那么拷贝语义会被用来进行置换。这就跟普通的置换语句是相同的了。

因此在移动语义的支持下,我们仅仅通过一个通用的模板,就可能更高效地完成置换。

综上所述:

实际上,为了保证移动语义的传递,程序员在编写移动构造函数的时候,应该总是记得使用std::move转换拥有形如堆内存、文件句柄等资源的成员为右值。

这样一来,如果成员支持移动构造的话,就可以实现其移动语义。

而即使成员没有移动构造函数,那么接受常量左值的构造函数版本也会轻松地实现拷贝构造,因此也不会引起大的问题。

good good study, day day up.

顺序 选择 循环 总结

原文地址:https://www.cnblogs.com/Braveliu/p/12235292.html

时间: 2024-10-07 20:05:27

C++11 std::move 强制转换为右值的相关文章

强制转换为右值

在C++11中,标准库在<utility>中提供了一个有用的函数std::move,这个函数的名字具有迷惑性,因为实际上std::move并不能移动任何东西,它唯一的功能是将一个左值强制转化为右值引用,继而我们可以通过右值引用使用该值,以用于移动语义.从实现上讲,std::move基本等同于一个类型转换: static_cast<T&&>(lvalue); 值得一提的是,被转化的左值,其生命期并没有随着左右值的转化而改变. #include <iostream

(译)C++11中的Move语义和右值引用

郑重声明:本文是笔者网上翻译原文,部分有做添加说明,所有权归原文作者! 地址:http://www.cprogramming.com/c++11/rvalue-references-and-move-semantics-in-c++11.html C++一直致力于生成快速的程序.不幸的是,直到C++11之前,这里一直有一个降低C++程序速度的顽症:临时变量的创建.有时这些临时变量可以被编译器优化(例如返回值优化),但是这并不总是可行的,通常这会导致高昂的对象复制成本.我说的是怎么回事呢? 让我们

【C++11新概念】:右值引用

C语言原始定义:在C语言中表示位于赋值运算符两侧的两个值,左边的就叫左值,右边的就叫右值. 左值: 地址,内存中的具体空间,可以被读写:例如变量 左值指的是如果一个表达式可以引用到某一个对象,并且这个对象是一块内存空间且可以被检查和存储 右值: 数据,例如1,‘哈哈哈哈’ 右值指的是引用了一个存储在某个内存地址里的数据.不能通过引用或指针读写.用户无法控制这个右值. 一个区分左值和右值的方法是:能不能对这个值取地址. return语句: 按照以前C的说法,return语句如果是按值传递的话,re

我是如何明白C++的move semantics(右值引用)和perfect forwarding(完美转发)的

其实主要就是三篇文章(博客): 首推这篇. http://thbecker.net/articles/rvalue_references/section_01.html 从这里你可以知道什么时候你会知道,什么时候能够 “链式地” 调用移动构造函数而什么时候不能 ,明白其中过程(特别是什么时候不能)的一些细微差别,你就差不多懂什么是move semantic了. 然后是scott meyers的Universal Reference In C++11: https://isocpp.org/blo

C/C++ C++ 11 std::move()

{ 0. C++ 标准库使用比如vector::push_back 等这类函数时,会对参数的对象进行复制,连数据也会复制.这就会造成对象内存的额外创建, 本来原意 是想把参数push_back进去就行了,通 过std::move,可以避免不必要的拷贝操作. 1. std::move是将对象的状态或者所有权从一个对象转移到另一个对象,只是转移,没有内存的搬迁或者内存拷贝所以可以提高利用效率,改善性能.. 2. 对指针类型的标准库对象并不需要这么做. } 原文地址:https://www.cnblo

6.11 将分割数据转换为多值IN列表

问题 已经有了分隔数据,想要将其转换为WHERE子句IN列表中的项目.考虑下面的字符串: 7654,7698,7782,7788 要将该字符串用在WHERE子句中,但是下面的SQL语句是错误的,因为EMPNO是一个数值列: select ename,sal,deptno from emp where empno in ( '7654,7698,7782,7788' ) 因为EMPNO是一个数值列,而此IN列表是一个字符串值,所以此SQL语句会失败.现要将此字符串转换为用逗号分解的数值列表. 解决

格式工厂(四) 右值引用

版权声明:本文为博主原创文章,未经博主允许不得转载. 由于右值所产生的临时变量问题一直是一种诟病,C++11中引入了右值引用这个核心语言机制,来提升运行期性能. 首先我先说明一下什么是左值和右值: 左值和右值都是针对表达式而言的,左值是指表达式结束后依然存在的持久对象,右值是指表达式结束时就不再存在的临时对象 int b = 20; //这里b是左值 20是右值 ,因为这个表达式过后 20将不存在了 而b依然存在 //同时也可以根据能否取地址来判断是否是左值(能取地址)还是右值(不能取地址),如

【转载】C++ 11中的右值引用

本篇随笔为转载,原博地址如下:http://www.cnblogs.com/TianFang/archive/2013/01/26/2878356.html 右值引用的功能 首先,我并不介绍什么是右值引用,而是以一个例子里来介绍一下右值引用的功能: #include <iostream>    #include <vector>    using namespace std; class obj    {    public :        obj() { cout <&l

C++ 11 中的右值引用

C++ 11 中的右值引用 右值引用的功能 首先,我并不介绍什么是右值引用,而是以一个例子里来介绍一下右值引用的功能: #include <iostream>    #include <vector>    using namespace std; class obj    {    public :        obj() { cout << ">> create obj " << endl; }        obj(c