所有权和移动
参考文章
前言
首先有那么几个问题:从 C++98 升级到 C++11 能提升性能吗?从函数中返回 STL 容器的开销大吗?return std::move(x) 有意义吗?
这些问题都牵扯到了移动语义和复制构造。
解释
1 |
|
分析这段代码:
- X 类型,它是一个空类,拥有的这些函数也只是方便我们观察,假设它是一个用来扮演我们平时的智能指针或者说是 std::vector 之类的容器。
- Test 这个类型用来扮演正常的,支持 C++11 移动语义,遵守所有权转移这个君子协议的类类型(如 STL 容器,智能指针)。
- func 函数,是否拷贝自己保有的 X,以及为什么。
- main 函数用花括号分出了三个作用域,也就代表了三个例子。
- 第一个作用域打印出的结果是:Test t{new X};首先 new X,申请了构造X,调用 X 的构造函数,打印了 X() 。返回了一个指针,调用了 Test 的有参构造,用来初始化它的数据成员 m_p。t 拥有了 X 的所有权。t.empty() 的结果自然为 0,非空。
1
2
3
4
5X()
0
---------
1
~X()
打印了一个分割线。Test t2{ std::move(t)}; std::move(t)是一个亡值表达式,调用t2的移动构造。
即:将t1的m_p指针赋值给t2,并给t1的m_p赋空。完成了所有权的转移,t1不再拥有X的所有权,t2拥有了X的所有权。
所以后面的t.empty()会打印1,因为此时t的m_p已经为空了,m_p == nullptr,理所应当。
- 第二个作用域打印的结果:前两行和第一个作用域的一样,Test t2{ t }; 这里调用 Test 的复制构造。即:用 t 的 m_p 去用做 t2.m_p = new X(*t.m_p) 相当于调用 X 的复制构造进行初始化 。
1
2
3
4
5
6
7X()
0
---------
X(const X&)
0
~X()
~X()
与移动构造不一样,并不是直接转移指针,而是实实在在的 new 构造对象。t2 和 t1 拥有的X对象是一样的,但是不是同一个(因为是用了t的进行初始化,但是我们这是空类,不用在意,演示一下而已)。
t.empty() 的结果为 0,因为 t 并没有被转移所有权,t 依然拥有 X。 最终打印了两个析构,因为 t1 和 t2 各拥有一个 X
移动语义的诞生,就是为了方便区分,到底是需要移动还是真的需要拷贝。
- 第三个作用域打印的结果:我们拿STL的容器vector进行对照展示,方便理解。
1
2
3X()
---------
~X()运行结果:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
struct X {
X() { puts("X()"); }
X(const X&) { puts("X(const X&)"); }
X(X&&)noexcept { puts("X(X&&)"); }
~X() { puts("~X()"); }
};
std::vector<X> func(){
std::vector<X>v{X{}};
puts("------------");
return v;
}
int main(){
auto result = func();
}这是设置到C++17的打印结果,如果低一些,分割线之前会有更多的打印。1
2
3
4
5X()
X(const X&)
~X()
------------
~X()
这个vector的运行结果和我们的第三个作用域打印的结果,或者说他们的代码有什么相同点吗?
对,没错,分割线后没有再打印构造函数,代表没有拷贝自己实际存储的元素。上面两段代码的语境下,return都只会调用移动构造,来转移所有权。
我们回到Test的移动构造的实现,只是转移指针,无拷贝。std::vector同理。 完成了所有权的转移。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 luseYang的妙妙屋!