c++ 11学习笔记--右值引用和移动构造语义

今天我决定尝试用另外一种方式来表达,任何新语法的的产生都是为了解决某个问题,所以今天先看问题。

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
class myStr
{
protected:
char* str_;

public:
myStr(void) // 默认的构造函数,什么也不做
: str_(nullptr)
{}

myStr(const char* rhs) // 普通赋值构造函数
: str_(nullptr)
{
if (!rhs) return;
str_ = new char[1024];
strcpy(str_, rhs);
// cout << "Str constructor " << str_ << std::endl;
}

myStr(const myStr& rhs) // 拷贝构造函数
: str_(nullptr)
{
if (!rhs) return;
str_ = new char[1024];
strcpy(str_, rhs.str_);
// cout << "Str copy constructor " << str_ << std::endl;
}

myStr(myStr&& rhs)
: str_(nullptr)
{
swap(rhs);
// std::cout << "Str move constructor " << str_ << std::endl;
}

~myStr() // 析构函数
{
if (!str_) return;
// std::cout << "Str destructor " << str_ << std::endl;
delete [] str_;
}

const myStr& operator=(myStr rhs) // 赋值操作符重载
{
rhs.swap(*this); // 使用copy-and-swap惯用法获得数据
return (*this); // 避免重复撰写operator=
}

void swap(myStr& rhs) // 交换算法
{
std::swap(str_, rhs.str_);
}

operator char*(void) const
{
return str_;
}

myStr& operator+=(const char* rhs)
{
if (rhs) strcat(str_, rhs);
return (*this);
}

// friend myStr operator+(const myStr& x, const myStr& y)
// {
// return myStr(x) += y;
// }
friend myStr operator+(const myStr& x, const myStr& y)
{
return std::move(myStr(x) += y);
}

执行下面的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
myStr ss("000");
myStr s1("11111"), s2("22222"), s3("3333333"), s4("4444444");
cout << std::endl;
time_t timestamp1;
time_t timestamp2;
time_t timestamp3;

const long long max = 30000000;
time(&timestamp1);

for (long long i = 0; i<max; i++) {
ss = s1 + s2 + s3 + s4;
}
time(&timestamp2);

timestamp3 = timestamp2 - timestamp1;

下面的代码是唯一不同的实现,但是却带来30-40%的性能差距。

1
2
3
4
5
6
7
8
/    friend myStr operator+(const myStr& x, const myStr& y)
// {
// return myStr(x) += y;
// }
friend myStr operator+(const myStr& x, const myStr& y)
{
return std::move(myStr(x) += y);
}

再找一个例子

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
class MemoryBlock
{
public:

// 构造器(初始化资源)
explicit MemoryBlock(size_t length)
: _length(length)
, _data(new int[length])
{
std::cout << "MemoryBlock constructor " << std::endl;
}

// 析构器(释放资源)
~MemoryBlock()
{
if (_data != nullptr)
{
delete[] _data;
}
std::cout << "MemoryBlock destructor " << std::endl;
}

// 拷贝构造器(实现拷贝语义:拷贝that)
MemoryBlock(const MemoryBlock& that)
// 拷贝that对象所拥有的资源
: _length(that._length)
, _data(new int[that._length])
{
std::copy(that._data, that._data + _length, _data);
std::cout << "copy constructor " << std::endl;
}

// 拷贝赋值运算符(实现拷贝语义:释放this + 拷贝that)
MemoryBlock& operator=(const MemoryBlock& that)
{
if (this != &that)
{
// 释放自身的资源
delete[] _data;

// 拷贝that对象所拥有的资源
_length = that._length;
_data = new int[_length];
std::copy(that._data, that._data + _length, _data);
}
return *this;
}

// 移动构造器(实现移动语义:移动that)
MemoryBlock(MemoryBlock&& that)
// 将自身的资源指针指向that对象所拥有的资源
: _length(that._length)
, _data(that._data)
{
// 将that对象原本指向该资源的指针设为空值
that._data = nullptr;
that._length = 0;
}

// 移动赋值运算符(实现移动语义:释放this + 移动that)
MemoryBlock& operator=(MemoryBlock&& that)
{
if (this != &that)
{
// 释放自身的资源
delete[] _data;

// 将自身的资源指针指向that对象所拥有的资源
_data = that._data;
_length = that._length;

// 将that对象原本指向该资源的指针设为空值
that._data = nullptr;
that._length = 0;
}
return *this;
}
private:
size_t _length; // 资源的长度
int* _data; // 指向资源的指针,代表资源本身
};

MemoryBlock f() { return MemoryBlock(50); }

执行下面的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
const long long max = 100000;
time_t timestamp1;
time_t timestamp2;
time_t timestamp3;

time(&timestamp1);
for (long long i = 0; i<max; i++)
{
MemoryBlock a = MemoryBlock(50);

MemoryBlock c = std::move(a);
}

time(&timestamp2);
timestamp3 = timestamp2 - timestamp1;

如果把MemoryBlock c = std::move(a)换成MemoryBlock c = a;

性能上大概也有30%的差距。

这就是右值引用和移动构造语义带来的好处,我理解就是以前只能引用左值,而右值是不能引用的,新语法的加入实现了右值的引用,减少了零时对象的产生销毁,但是也带来了更多怪异的语法,明显增加了c++的学习成本,如果语法设计角度,像oc一样增加类似引用计数器来管理对象,会不会更加优雅一下,至少让上层的码农不会那么累,其实通智能指针也能达到同样的效果。

这么多年了c++都在做加法,让学习,使用成本太高了,标准委员为的大爷些什么时候考虑一下做点减法呢,不要让c++那么学院派或者满地都是陷阱,也不要让实现一种技术有10种方法,但是有5种都是陷阱。靠!