c++11 - c++move构造函数问题
问题描述
先贴代码,希望各位高手可以帮忙解答一下
class MyString {
public:
MyString() : data_(NULL), len_(0) { }
~MyString() {
if (data_ != NULL)
delete data_;
}
explicit MyString(const char *s) {
InitData(s);
}
MyString(const MyString &rhs) {
InitData(rhs.data_);
cout << "Copy constructor is called. source: " << rhs.data_ << endl;
}
MyString &operator=(const MyString &rhs) {
if (this != &rhs) {
if (data_ != NULL) delete data_;
InitData(rhs.data_);
}
cout << "Copy assignment is called. source: " << rhs.data_ << endl;
return *this;
}
MyString(MyString &&rhs) {
cout << "Move constructor is called. source: " << rhs.data_ << endl;
len_ = rhs.len_;
data_ = rhs.data_;
rhs.len_ = 0;
rhs.data_ = NULL;
}
MyString &operator=(MyString &&rhs) {
cout << "Move assignment is called. source: " << rhs.data_ << endl;
if (this != &rhs) {
len_ = rhs.len_;
data_ = rhs.data_;
rhs.len_ = 0;
rhs.data_ = NULL;
}
return *this;
}
const char *Get() const {
return data_;
}
private:
char *data_;
size_t len_;
void InitData(const char *s) {
len_ = strlen(s);
data_ = new char[len_ + 1];
memcpy(data_, s, len_);
data_[len_] = 0x0;
}
};
int main() {
vector<MyString> vec;
MyString a("hello"), b("world");
vec.push_back(move(a));
vec.push_back(move(b));
}
上面代码的输出结果是
Move constructor is called. source: hello
Move constructor is called. source: world
Copy constructor is called. source: hello
move构造被调用我能理解。但是最后那个拷贝构造函数是怎么乱入的啊,而且偏偏是1条,机器是mac,使用clang++编译,希望可以解答下这个疑问。
楼上解释了为什么会调用拷贝构造函数,我再给你解释一下为什么会乱入。
首先本质原因是vector扩容,一开始容量是0,第一次操作扩容到1,第二次是翻倍为2。
你是用mac下的clang++的,它调用的stl实现应该是libcxx,我们可以通过libcxx里vector的push_back实现源码看出来。
源码可以在这里查看 https://github.com/llvm-mirro...
if (this->__end_ != this->__end_cap())
{
}
else
__push_back_slow_path(__x);
__push_back_slow_path的实现是这样的
allocator_type& __a = this->__alloc();
__split_buffer<value_type, allocator_type&> __v(__recommend(size() + 1), size(), __a);
__alloc_traits::construct(__a, _VSTD::__to_raw_pointer(__v.__end_), _VSTD::forward<_Up>(__x));
__v.__end_++;
__swap_out_circular_buffer(__v);
可以看出是先扩容,__recommend就是做扩容的工作,随后把新的内容构造出来放在__a的后半段的首位,注意有forward,所以可移动也可拷贝,最后再执行__swap_out_circular_buffer函数。
__swap_out_circular_buffer的实现是这样的:
template <class _Tp, class _Allocator>
void
vector<_Tp, _Allocator>::__swap_out_circular_buffer(__split_buffer<value_type, allocator_type&>& __v)
{
__annotate_delete();
__alloc_traits::__construct_backward(this->__alloc(), this->__begin_, this->__end_, __v.__begin_);
_VSTD::swap(this->__begin_, __v.__begin_);
_VSTD::swap(this->__end_, __v.__end_);
_VSTD::swap(this->__end_cap(), __v.__end_cap());
__v.__first_ = __v.__begin_;
__annotate_new(size());
__invalidate_all_iterators();
}
对于迭代器的处理和对于size的处理可以不用看,重点是__alloc_traits::__construct_backward,它负责把之前vector的数据拷贝或者移动到新vector内存区。
__construct_backward的实现是这样的
while (__end1 != __begin1)
{
construct(__a, _VSTD::__to_raw_pointer(__end2-1), _VSTD::move_if_noexcept(*--__end1));
--__end2;
}
可以从move_if_noexcept看出它要执行移动操作必须保证是noexcept的,而且这个操作是从end开始的,迭代器一直递减到begin,所以是逆序的。
所以在你执行第一次push_back时,检查了容量不够,执行__push_back_slow_path函数,vector扩容到1,并且执行了一次移动构造函数,因为原来的vector是空的,所以不需要进一步处理。
此时就是你的第一次打印 Move constructor is called. source: hello
在你执行第二次push_back时,检查了容量不够,执行__push_back_slow_path函数,vector扩容到2,并且执行了一次移动构造函数。
此时就是你的第二次打印 Move constructor is called. source: world
随后它执行__swap_out_circular_buffer
,并调用__alloc_traits::__construct_backward
,由于你的移动构造函数不是noexcept的,所以它调用了一次你的拷贝构造函数。
此时就是你的第三次打印 Copy constructor is called. source: hello
如果你有更多的元素,你就会发现后续的拷贝构造函数执行顺序是和原vector的顺序反着的,原因上面也说了,操作是从end开始的,迭代器一直递减到begin。
这篇关于c++11 - c++move构造函数问题的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!