您的位置:首页 > 编程语言 > C语言/C++

C++ primer 当vector中的push_back遇到移动构造函数会发生什么

2018-03-20 21:48 309 查看

首先仔细阅读 C++ primer 第五版 P474 Note下面的一段话

意思是说当我们在类中定义了
移动构造函数
的时候,假设这个移动构造函数是
noexcept
的,类似对应
StrVec
类的操作,
vector
可能会重新分配内存,也就是说会将元素从旧空间移动到新内存中去。这个过程中就会发生
移动构造函数


注意

这里要注意一点,C++11规定的是移后源对象是一定要处于可析构的状态,意思是说移后源对象有可能执行析构函数,也有可能不执行析构函数,主要要看这个源对象在这个作用域生命周期是否结束吗,以下有例子可以说明。

默认初始化的vector是不分配内存空间的,当push_back发现vector空间不足以容纳新元素时,就会分配新的空间(通常是加倍),将数据移动到新空间时就会发生
移动构造函数
,而当我们用
vs.reserve()
预留足够的空间时,就不会发生移动构造函数了。


例子

使用C++ primer 中的
String
类,这里我使用
MyString
,内容几乎一样。

1.代码如下:

int main()
{
MyString s1("FOne"), s2("Two");
vector<MyString> msvec;
msvec.push_back(s1);
msvec.push_back(s2);
//msvec.push_back(std::move(s2));
//msvec.push_back(MyString("Three"));
//msvec.push_back("Four");
//system("pause");

return 0;
}


结果如下:



前三行容易理解,拷贝构造函数(push_back(s1))之后,发生了
移动构造函数
析构函数
,之后才又发生了一次拷贝构造函数。

原因就是开头的那一段话,由于我们这个类已经定义了移动构造函数,当push_back时候,会发生内存的重新分配,即将已经push_back进去的s1给移动到新内存,而这个移动后源对像是可析构的,并且应该销毁的,因为不再需要他了。故发生了
析构函数
,之后再有
拷贝构造函数


最后的四行
析构函数
也不难理解了,将s1 s2 vector中的s1 s2给析构了。

2.代码如下

int main()
{
MyString s1("FOne"), s2("Two");
vector<MyString> msvec;
msvec.push_back(s1);
//msvec.push_back(s2);
//msvec.push_back(std::move(s2));
//msvec.push_back(MyString("Three"));
msvec.push_back("Four");
//system("pause");

return 0;
}


结果如下:



和情况1大致相同,只是用
"Four"
来构造了一个MyString实体。同样发生两次的
移动构造函数
析构函数


3.代码如下:

int main()
{
MyString s1("FOne"), s2("Two");
vector<MyString> msvec;
msvec.push_back(s1);
//msvec.push_back(s2);
msvec.push_back(std::move(s2));
//msvec.push_back(MyString("Three"));
//msvec.push_back("Four");
system("pause");

return 0;
}


结果如下:



注意这次情况,我们push_back的是
std::move(s2)
,move函数将返回的是一个
右值
,尽管s2是一个左值,那么此时发生
移动构造函数
之后,移后源对象并没有直接发生析构,因为在这个作用域中s2的生命周期并未结束,而当return 0;

更要注意

结束的时候,发生了四次析构函数。这四次析构和第一种情况的四次析构并不一样。因为这个情况中的有一次析构,是析构移动后的s2,移动后的s2中并没有资源了,也就是说他的指针应该为
nullptr
size
应该为0。而对于第一种情况,四次析构全是有实体的,也就是说四次析构,析构的全是已经分配内存资源的实体。

通过调试,可以看到现在这个情况的四次析构:

第一次析构:



析构s1(vector中的),即
"FOne"


第二次析构:



此时析构s2(vector中的),即
"Two"


第三次析构:



这次很特殊,
p
nullptr
,sz为0 ,也就是说此时我们释放的是已经移动后的s2,也就是移后源对像,因为他把资源交给了其他,故才有
p
nullptr
,sz为0这种情况。

第四次析构:



这次就是释放s1,很正常的
"FOne"


对比第一种情况的四次析构,第一种情况的四次析构,都是销毁已经分配内存空间的实体,可以自己去调试。

总结:

1.C++11规定的是移后源对象是一定要处于可析构的状态,意思是说移后源对象有可能执行析构函数,也有可能不执行析构函数,视情况而定。

2.当我们在类中定义了
移动构造函数
push_back
vector
可能会重新分配内存,也就是说会将元素从旧空间移动到新内存中去。这个过程中就会发生
移动构造函数


3.默认初始化的vector是不分配内存空间的,当push_back发现vector空间不足以容纳新元素时,就会分配新的空间(通常是加倍),将数据移动到新空间时就会发生
移动构造函数
,而当我们用
vs.reserve()
预留足够的空间时,就不会发生移动构造函数了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息