我在理解使用移动语义和对象的向量的概念时遇到问题。我将放置代码并确切地向您解释问题,因为我真的很想了解其背后的逻辑。
#include <iostream>
#include <vector>
#include <cstring>
#include "Mystring.h"
using namespace std;
class Mystring {
private:
char* str;
public:
Mystring();
Mystring(const char* s);
Mystring(const Mystring& source);
Mystring(Mystring&& source);
~Mystring();
Mystring& operator=(const Mystring& rhs);
Mystring& operator=(Mystring&& rhs);
void display() const;
int get_lenght() const;
const char* get_str() const;
};
int main() {
vector <Mystring> stooges_vec;
stooges_vec.push_back("Larry"); //11
stooges_vec.push_back("Moe"); //12
stooges_vec.push_back("Curly"); //13
return 0;
}
//One-args constructor
Mystring::Mystring(const char* s)
: str{ nullptr } {
if (s == nullptr) {
str = new char[1];
*str = '\0';
}
else {
str = new char[std::strlen(s) + 1];
std::strcpy(str, s);
}
}
//Copy constructor
Mystring::Mystring(const Mystring& source)
:str{ nullptr } {
str = new char[std::strlen(source.str) + 1];
std::strcpy(str, source.str);
}
//Move constructor
Mystring::Mystring(Mystring&& source)
:str{ source.str } {
source.str = nullptr;
std::cout << "Move constructor called." << std::endl;
}
//Destructor
Mystring::~Mystring() {
delete[] str;
}
//Copy assignment
Mystring& Mystring::operator=(const Mystring& rhs) {
std::cout << "Copy assignment called." << std::endl;
if (this == &rhs)
return *this;
delete[] str;
str = new char[std::strlen(rhs.str) + 1];
std::strcpy(str, rhs.str);
return *this;
}
//Move assignment
Mystring& Mystring::operator=(Mystring&& rhs) {
std::cout << "Move assignment called." << std::endl;
if (this == &rhs)
return *this;
delete[] str;
str = rhs.str;
rhs.str = nullptr;
return *this;
}
所以,问题是我不了解该程序的过程。我拿了调试器,一切都很好:
对于要添加的第一个对象,首先调用“一个args构造函数”,以便创建我的对象,然后调用“ move构造函数”,以便我窃取数据并使null的指针为空。原始对象。我完成它,将我的对象推入向量中,然后销毁现在位于其中带有空指针的原始对象。
在这里,我会永远迷失方向,因为过程看起来相同,除了在std::cout << "Move constructor called." << std::endl;
行之后,控件转到了复制构造函数,我注意到(我希望我没有错)他Larry
的副本。
问题是:我不知道编译器为什么要复制Larry
。它在那里,所以编译器不能简单地创建Moe
,使用move语义并仅将Moe
推到向量的后面吗?为什么必须要复印?
另外,对我来说,一个非常奇怪的行为是我看到一个析构函数在Larry
的副本构造函数之后被调用,因为我看到它被销毁了。我的意思是,此时编译器将销毁什么?原始的Larry
会保留副本,或在那里到底发生了什么?
有人可以向我解释第二push_back()
到底发生了什么吗?
答案 0 :(得分:1)
实际上,如果您在ideone上运行代码:
您只会看到3个移动ctor:
Move constructor called.
Move constructor called.
Move constructor called.
但这取决于编译器中使用的std::vector
的特定实现。您似乎在插入第一个元素之后,添加第二个元素之前重新分配了空间,因此std::vector
必须使用copy ctor将第一个对象复制到新位置并销毁旧的对象。由于您没有移动ctor noexcept
,因此无法移动该对象。其他详细信息可以在这里找到:
How to enforce move semantics when a vector grows?
要验证这是原因,只需在构造向量后添加一行:
vector <Mystring> stooges_vec;
stooges_vec.reserve( 3 );
stooges_vec.push_back("Larry"); //11
stooges_vec.push_back("Moe"); //12
stooges_vec.push_back("Curly"); //13
这将防止重新分配btw对象插入。当然,要完全解决您的问题,请使用移动控制器和赋值运算符noexcept