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

问题来自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;
}

结果是这样的:

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

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

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

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

结果如下:

这次在push_back之前加了一句

vec.reserve(4);

于是不再需要分配空间。

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

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

 

  • 7
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值