欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

关于c++中vector的push_back、拷贝构造copy constructor和移动构造move constructor

程序员文章站 2022-03-22 21:34:46
...

问题来自C++ Primer的第十三章练习题的13.48.是这样说的:

定义一个vector<String>并在其上多次调用push_back运行你的程序,并观察String被拷贝了多少次。

其中String是在习题中自己写的一个类似标准库string的类。(第一次看,自己写的String类确实有点挫,于是直接用了github上找到的CppPrimer答案中的String类。

//String.h

class String {
public:
	String() : String("") {}
	String(const char*);
	String(const String&);
	String& operator=(const String&);
	String(String&&);
	String& operator=(String&&);
	~String();

	const char* c_str() const { return elements; }
	size_t size() const { return end - elements; }
	size_t length() const { return end - elements - 1; }

private:
	std::pair<char*, char*> alloc_n_copy(const char*, const char*);
	void range_initializer(const char*, const char*);
	void free();

private:
	char* elements;
	char* end;
	std::allocator<char> alloc;
};

std::pair<char*, char*> String::alloc_n_copy(const char* b, const char* e)
{
	auto str = alloc.allocate(e - b);
	return{ str, std::uninitialized_copy(b, e, str) };
}
void String::range_initializer(const char* first, const char* last)
{
	auto newstr = alloc_n_copy(first, last);
	elements = newstr.first;
	end = newstr.second;
}
String::String(const char* s)
{
	char* sl = const_cast<char*>(s);
	while (*sl) ++sl;
	range_initializer(s, ++sl);
}
String::String(const String& rhs)
{
	range_initializer(rhs.elements, rhs.end);
	std::cout << "copy constructor" << std::endl;
}

String& String::operator=(const String& rhs)
{
	auto newstr = alloc_n_copy(rhs.elements, rhs.end);
	free();
	elements = newstr.first;
	end = newstr.second;
	std::cout << "copy-assignment operator" << std::endl;
	return *this;
}
String::String(String &&s) :elements(s.elements), end(s.end)
{
	s.elements = s.end = nullptr;
	cout << "move constructor" << endl;
}
String& String::operator=(String &&s)
{
	if (this != &s)
	{
		free();
		elements = s.elements;
		end = s.end;
		s.elements = s.end = nullptr;
	}
	cout << "move-assignment operator" << endl;
	return *this;
}
void String::free()
{
	if (elements) {
		std::for_each(elements, end, [this](char& c) { alloc.destroy(&c); });
		alloc.deallocate(elements, end - elements);
	}
}
String::~String()
{
	free();
}

String类中定义了拷贝构造和移动构造函数,并且在调用的时候打印提示信息。

一开始遇到的问题是(那时候书上还没有讲到移动构造函数,相当于把上面头文件中的移动构造给注释掉,只有拷贝构造),我随便写了测试:

void printInfo(vector<String> &v)
{
	cout << "size:" << v.size() << "  " << "capacity:" <<  v.capacity() << endl;
}
int main()
{
	String s0("hello");
	vector<String> vec;
	printInfo(vec);
	vec.push_back(s0);
	printInfo(vec);
	vec.push_back(s0);
	printInfo(vec);
	vec.push_back(s0);
	printInfo(vec);
	vec.push_back(s0);
	printInfo(vec);
	
	system("pause");
	return 0;
}

结果是这样的:

关于c++中vector的push_back、拷贝构造copy constructor和移动构造move constructor

由于push_back是调用的copy constructor,可以看到每一次push_back除了调用一次copy constructor外还要额外调用多次。当时我没想到是因为vector中的预留空间不足,需要额外分配空间(其实就是这个原因)。

而以上是没有移动构造的时候。下面加入了移动构造后,结果变成了:

关于c++中vector的push_back、拷贝构造copy constructor和移动构造move constructor

这次更加直观了,可以看到,每次push_back在copy之前还要首先将已有的元素通过调用move constructor来移动,留出空间,然后再copy添加元素。之前之所以调用的全是copy constructor是因为没有定义move constructor,而此时编译器也不会自动合成(primer后面有说),这时候只能调用拷贝构造,效率就降低了。

那么可以预料,如果在测试之前首先调用vector的reserve方法预留出空间,那么就不需要一次次调用move constructor了。

结果如下:

关于c++中vector的push_back、拷贝构造copy constructor和移动构造move constructor

这次在push_back之前加了一句

vec.reserve(4);

于是不再需要分配空间。

结论:vector添加元素在空间不够的时候需要重新分配,此时对于原来已有的元素会调用对象类的move constructor,而如果对象类没有定义移动构造,则会 使用copy constructor。所以给类添加移动构造函数可以在很多时候提高效率,因为没有它只能用拷贝构造来替代,而编译器又不会自动合成一个。

Cpp Primer是本好书,就是第一次啃有点慢= =。

 

相关标签: c primer push_back