C++11之 Move semantics(移动语义)(转)
转https://blog.csdn.net/wangshubo1989/article/details/49748703
按值传递的意义是什么?
当一个函数的参数按值传递时,这就会进行拷贝。当然,编译器懂得如何去拷贝。
而对于我们自定义的类型,我们也许需要提供拷贝构造函数。
但是不得不说,拷贝的代价是昂贵的。
所以我们需要寻找一个避免不必要拷贝的方法,即C++11提供的移动语义。
上一篇博客中有一个句话用到了:
#include <iostream>
void f(int& i) { std::cout << "lvalue ref: " << i << "\n"; }
void f(int&& i) { std::cout << "rvalue ref: " << i << "\n"; }
int main()
{
int i = 77;
f(i); // lvalue ref called
f(99); // rvalue ref called
f(std::move(i)); // 稍后介绍
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
实际上,右值引用注意用于创建移动构造函数和移动赋值运算。
移动构造函数类似于拷贝构造函数,把类的实例对象作为参数,并创建一个新的实例对象。
但是 移动构造函数可以避免内存的重新分配,因为我们知道右值引用提供了一个暂时的对象,而不是进行copy,所以我们可以进行移动。
换言之,在设计到关于临时对象时,右值引用和移动语义允许我们避免不必要的拷贝。我们不想拷贝将要消失的临时对象,所以这个临时对象的资源可以被我们用作于其他的对象。
右值就是典型的临时变量,并且他们可以被修改。如果我们知道一个函数的参数是一个右值,我们可以把它当做一个临时存储。这就意味着我们要移动而不是拷贝右值参数的内容。这就会节省很多的空间。
下面这个图,很好地说明了拷贝构造函数和移动构造函数的区别。
看明白了吗?
通俗一点的解释就是,拷贝构造函数中,对于指针,我们一定要采用深层复制,而移动构造函数中,对于指针,我们采用浅层复制。
但是上面提到,指针的浅层复制是非常危险的呀。没错,确实很危险,而且通过上面的例子,我们也可以看出,浅层复制之所以危险,是因为两个指针共同指向一片内存空间,若第一个指针将其释放,另一个指针的指向就不合法了。所以我们只要避免第一个指针释放空间就可以了。避免的方法就是将第一个指针(比如a->value)置为NULL,这样在调用析构函数的时候,由于有判断是否为NULL的语句,所以析构a的时候并不会回收a->value指向的空间(同时也是b->value指向的空间)
所以我们可以把上面的拷贝构造函数的代码修改一下:
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <vector> using namespace std; class Str{
public:
char *value;
Str(char s[])
{
cout<<"调用构造函数..."<<endl;
int len = strlen(s);
value = new char[len + 1];
memset(value,0,len + 1);
strcpy(value,s);
}
Str(Str &v)
{
cout<<"调用拷贝构造函数..."<<endl;
this->value = v.value;
v.value = NULL;
}
~Str()
{
cout<<"调用析构函数..."<<endl;
if(value != NULL)
delete[] value;
}
}; int main()
{ char s[] = "I love BIT";
Str *a = new Str(s);
Str *b = new Str(*a);
delete a;
cout<<"b对象中的字符串为:"<<b->value<<endl;
delete b;
return 0;
}
结果为:
修改后的拷贝构造函数,采用了浅层复制,但是结果仍能够达到我们想要的效果,关键在于在拷贝构造函数中,最后我们将v.value置为了NULL,这样在析构a的时候,就不会回收a->value指向的内存空间。
这样用a初始化b的过程中,实际上我们就减少了开辟内存,构造成本就降低了。
但要注意,我们这样使用有一个前提是:用a初始化b后,a我们就不需要了,最好是初始化完成后就将a析构。如果说,我们用a初始化了b后,仍要对a进行操作,用这种浅层复制的方法就不合适了。
所以C++引入了移动构造函数,专门处理这种,用a初始化b后,就将a析构的情况。
*************************************************************
**移动构造函数的参数和拷贝构造函数不同,拷贝构造函数的参数是一个左值引用,但是移动构造函数的初值是一个右值引用。(关于右值引用大家可以看我之前的文章,或者查找其他资料)。这意味着,移动构造函数的参数是一个右值或者将亡值的引用。也就是说,只用用一个右值,或者将亡值初始化另一个对象的时候,才会调用移动构造函数。而那个move语句,就是将一个左值变成一个将亡值。
移动构造函数应用最多的地方就是STL中
给出一个代码,大家自行验证使用move和不适用move的区别吧
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <vector>
using namespace std; class Str{
public:
char *str;
Str(char value[])
{
cout<<"普通构造函数..."<<endl;
str = NULL;
int len = strlen(value);
str = (char *)malloc(len + 1);
memset(str,0,len + 1);
strcpy(str,value);
}
Str(const Str &s)
{
cout<<"拷贝构造函数..."<<endl;
str = NULL;
int len = strlen(s.str);
str = (char *)malloc(len + 1);
memset(str,0,len + 1);
strcpy(str,s.str);
}
Str(Str &&s)
{
cout<<"移动构造函数..."<<endl;
str = NULL;
str = s.str;
s.str = NULL;
}
~Str()
{
cout<<"析构函数"<<endl;
if(str != NULL)
{
free(str);
str = NULL;
}
}
};
int main()
{
char value[] = "I love zx";
Str s(value);
vector<Str> vs;
//vs.push_back(move(s));
vs.push_back(s);
cout<<vs[0].str<<endl;
if(s.str != NULL)
cout<<s.str<<endl;
return 0;
}
说多无语,看代码:
#include <iostream>
#include <algorithm>
class A
{
public:
// Simple constructor that initializes the resource.
explicit A(size_t length)
: mLength(length), mData(new int[length])
{
std::cout << "A(size_t). length = "
<< mLength << "." << std::endl;
}
// Destructor.
~A()
{
std::cout << "~A(). length = " << mLength << ".";
if (mData != NULL) {
std::cout << " Deleting resource.";
delete[] mData; // Delete the resource.
}
std::cout << std::endl;
}
// Copy constructor.
A(const A& other)
: mLength(other.mLength), mData(new int[other.mLength])
{
std::cout << "A(const A&). length = "
<< other.mLength << ". Copying resource." << std::endl;
std::copy(other.mData, other.mData + mLength, mData);
}
// Copy assignment operator.
A& operator=(const A& other)
{
std::cout << "operator=(const A&). length = "
<< other.mLength << ". Copying resource." << std::endl;
if (this != &other) {
delete[] mData; // Free the existing resource.
mLength = other.mLength;
mData = new int[mLength];
std::copy(other.mData, other.mData + mLength, mData);
}
return *this;
}
// Move constructor.
A(A&& other) : mData(NULL), mLength(0)
{
std::cout << "A(A&&). length = "
<< other.mLength << ". Moving resource.\n";
// Copy the data pointer and its length from the
// source object.
mData = other.mData;
mLength = other.mLength;
// Release the data pointer from the source object so that
// the destructor does not free the memory multiple times.
other.mData = NULL;
other.mLength = 0;
}
// Move assignment operator.
A& operator=(A&& other)
{
std::cout << "operator=(A&&). length = "
<< other.mLength << "." << std::endl;
if (this != &other) {
// Free the existing resource.
delete[] mData;
// Copy the data pointer and its length from the
// source object.
mData = other.mData;
mLength = other.mLength;
// Release the data pointer from the source object so that
// the destructor does not free the memory multiple times.
other.mData = NULL;
other.mLength = 0;
}
return *this;
}
// Retrieves the length of the data resource.
size_t Length() const
{
return mLength;
}
private:
size_t mLength; // The length of the resource.
int* mData; // The resource.
};
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
移动构造函数
语法:
A(A&& other) noexcept // C++11 - specifying non-exception throwing functions
{
mData = other.mData; // shallow copy or referential copy
other.mData = nullptr;
}
- 1
- 2
- 3
- 4
- 5
最主要的是没有用到新的资源,是移动而不是拷贝。
假设一个地址指向了一个有一百万个int元素的数组,使用move构造函数,我们没有创造什么,所以代价很低。
// Move constructor.
A(A&& other) : mData(NULL), mLength(0)
{
// Copy the data pointer and its length from the
// source object.
mData = other.mData;
mLength = other.mLength;
// Release the data pointer from the source object so that
// the destructor does not free the memory multiple times.
other.mData = NULL;
other.mLength = 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
移动比拷贝更快!!!
移动赋值运算符
语法:
A& operator=(A&& other) noexcept
{
mData = other.mData;
other.mData = nullptr;
return *this;
}
- 1
- 2
- 3
- 4
- 5
- 6
工作流程这样的:Google上这么说的:
Release any resources that *this currently owns.
Pilfer other’s resource.
Set other to a default state.
Return *this.
// Move assignment operator.
A& operator=(A&& other)
{
std::cout << "operator=(A&&). length = "
<< other.mLength << "." << std::endl;
if (this != &other) {
// Free the existing resource.
delete[] mData;
// Copy the data pointer and its length from the
// source object.
mData = other.mData;
mLength = other.mLength;
// Release the data pointer from the source object so that
// the destructor does not free the memory multiple times.
other.mData = NULL;
other.mLength = 0;
}
return *this;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
让我们看几个move带来的好处吧!
vector众所周知,C++11后对vector也进行了一些优化。例如vector::push_back()被定义为了两种版本的重载,一个是cosnt T&左值作为参数,一个是T&&右值作为参数。例如下面的代码:
std::vector<A> v;
v.push_back(A(25));
v.push_back(A(75));
- 1
- 2
- 3
上面两个push_back()都会调用push_back(T&&)版本,因为他们的参数为右值。这样提高了效率。
而 当参数为左值的时候,会调用push_back(const T&) 。
#include <vector>
int main()
{
std::vector<A> v;
A aObj(25); // lvalue
v.push_back(aObj); // push_back(const T&)
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
但事实我们可以使用 static_cast进行强制:
// calls push_back(T&&)
v.push_back(static_cast<A&&>(aObj));
- 1
- 2
我们可以使用std::move完成上面的任务:
v.push_back(std::move(aObj)); //calls push_back(T&&)
- 1
似乎push_back(T&&)永远是最佳选择,但是一定要记住:
push_back(T&&) 使得参数为空。如果我们想要保留参数的值,我们这个时候需要使用拷贝,而不是移动。
最后写一个例子,看看如何使用move来交换两个对象:
#include <iostream>
using namespace std;
class A
{
public:
// constructor
explicit A(size_t length)
: mLength(length), mData(new int[length]) {}
// move constructor
A(A&& other)
{
mData = other.mData;
mLength = other.mLength;
other.mData = nullptr;
other.mLength = 0;
}
// move assignment
A& operator=(A&& other) noexcept
{
mData = other.mData;
mLength = other.mLength;
other.mData = nullptr;
other.mLength = 0;
return *this;
}
size_t getLength() { return mLength; }
void swap(A& other)
{
A temp = move(other);
other = move(*this);
*this = move(temp);
}
int* get_mData() { return mData; }
private:
int *mData;
size_t mLength;
};
int main()
{
A a(11), b(22);
cout << a.getLength() << ' ' << b.getLength() << endl;
cout << a.get_mData() << ' ' << b.get_mData() << endl;
swap(a,b);
cout << a.getLength() << ' ' << b.getLength() << endl;
cout << a.get_mData() << ' ' << b.get_mData() << endl;
return 0;
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
C++11之 Move semantics(移动语义)(转)的更多相关文章
- C++11新特性之 Move semantics(移动语义)
https://blog.csdn.net/wangshubo1989/article/details/49748703 这篇讲到了vector的push_back的两种重载版本,左值版本和右值版本.
- C++11的value category(值类别)以及move semantics(移动语义)
转载请保留以下声明 作者:赵宗晟 出处:http://www.cnblogs.com/zhao-zongsheng/p/value_categories_and_move_semantics.html ...
- c++11 移动语义move semantics
performance, expensive object copies move semantics, temporary objects implemented with rvalue refer ...
- 第15课 右值引用(2)_std::move和移动语义
1. std::move (1)std::move的原型 template<typename T> typename remove_reference<T>::type& ...
- stout代码分析之十:c++11之move和forward
stout中大量使用了c++11的特性,而c++11中move和forward大概是最神奇的特性了. 左值和右值的区别 ; // a是左值,0是右值 int b = rand(); // b是左值,r ...
- Move semantics(C++11)
/* * Compile with: * g++ move_test.c -o move_test -std=c++11 -g -fno-elide-constructors * ...
- C++11 std::move和std::forward
下文先从C++11引入的几个规则,如引用折叠.右值引用的特殊类型推断规则.static_cast的扩展功能说起,然后通过例子解析std::move和std::forward的推导解析过程,说明std: ...
- c++11 std::move()
简单点理解,c++11 中的std::move() 函数,实际上就是将一个左值强制转换成一个右值引用数据类型,从而可以调用相应的对右值引用重载的函数. 如果使用std::move() 的返回值做为参数 ...
- C++11 feature: move constructor
There are heaps of good articles out there about C++ features including this move constructor. Howev ...
随机推荐
- PIE SDK元素的保存与打开
1.功能简介 绘制元素之后需要对元素进一步的保存操作,可以利用PIE SDK的ExportElementsCommand命令保存成xml格式的文件,打开元素可以利用ImportElementsComm ...
- PIE SDK坐标系选择
1. 功能简介 坐标系选择可以查看当前图层的坐标系信息和显示其他坐标系的信息,下面将基于PIE SDK介绍如何实现坐标系选择功能. 2. 功能实现说明 2.1. 实现思路及原理说明 第一步 加载图层并 ...
- QWebView使用
问题: 开始编译的时候在pro文件中要加上 QT += core gui webkitwidgets 文件使用部分加上 #include<QtWebKitWidgets/QWebView&g ...
- HTML盒子尺寸的计算
参考链接http://edu.51cto.com/lesson/id-54739.html
- Python 断言的使用方法
自动化测试常用断言的使用方法(python) 自动化测试中寻找元素并进行操作,如果在元素好找的情况下,相信大家都可以较熟练地编写用例脚本了,但光进行操作可能还不够,有时候也需要对预期结果进行判断. 这 ...
- 使用bind配置DNS服务(CentOS 6.5)
DNS域名解析服务(Domain Name System)是用于解析域名与IP地址对应关系的服务,功能上可以实现正向解析与反向解析: 正向解析:根据主机名(域名)查找对应的IP地址. 反向解析:根据I ...
- 018-面向接口编程的BeanFactory模板代码
1 BeanFactory工具类 package www.test.utils; import org.dom4j.Document; import org.dom4j.Element; import ...
- pat1014. Waiting in Line (30)
1014. Waiting in Line (30) 时间限制 400 ms 内存限制 65536 kB 代码长度限制 16000 B 判题程序 Standard 作者 CHEN, Yue Suppo ...
- 【shell】《shell学习指南》读书笔记
一.使用shell脚本 优点:脚本语言能够轻易处理文件与目录之间的对象,如把文件从所有目录拷贝到另一个目录 缺点:效率不如编译型语言 二.简单的脚本 1.查看现在系统有谁登录 $who 2.算出行数 ...
- java实现截取6个汉字字母数字
项目中使用到需要截取6个字(12个字母或数字),解决方法如下: /** * * @方法名称:getWordCount * @内容摘要: <截取输入字符串 大于6个后显示...> * @pa ...