C++ primer 薄片系列之移动构造
来源:互联网 发布:股票历史复权数据接口 编辑:程序博客网 时间:2024/04/28 07:38
移动构造函数
为啥要有移动构造函数, 个人浅见,为避免调用成员的复制构造函数造成额外的开销,毕竟复制指针要来的更方便快捷,因而产生的。比如一个string对象,普通的拷贝赋值或者拷贝构造函数的方式中,我们是在内存空间里创建一个新的string对象,并将原string的char数组一个一个复制到新的string对象。移动构造或者移动赋值的时候,我们先假定原来的string,我们已经不需要用了。但是原来string对象里面的指向char的指针我们需要继续保留,不能让编译器在析构的时候,将这一指针指向的内存释放掉。那这个时候为了”废物利用”, 我们就直接将原来string的char指针给复制过来,同时让原来string的char指针指向nullptr。这样原来的string有价值的部分已经被瓜分干净了,可以被编译器”垃圾回收”了
//代码中以自定义结构体Str为基本元素,并对其实现移动构造和移动赋值操作,可以完美展现std::move和 普通赋值的差异class Str {public: Str(int t = 0) :i(new int(t)) { cout << "Here Str construct" << endl; } Str(const Str & t) { cout << "Here Str copy construct" << endl; i = new int(*t.i); } Str(Str && t)noexcept:i(t.i)//noexcept 通知标准库该函数不会抛出异常,否则标准库会认为移动时可能有异常,并且为了处理这种可能而做一些额外的操作 { cout << "Here Str move construct" << endl; t.i = nullptr; } Str& operator=(Str&& p) noexcept { if (this != &p) { delete i; i = p.i; p.i = nullptr; } cout << "Here Str move assign" << endl; return *this; } Str& operator=(Str& p) { auto tt = new int(*p.i); delete i; i = tt; cout << "Here Str assign" << endl; return *this; } ~Str() { if (i)//****当reallocate函数里以移动的方式进行构造时,旧的Str会在移动拷贝时i被设置为nullptr,因而不会进这个条件语句。而拷贝赋值的时候,旧的Str的i还存在,所以会进入条件语句,进行内存的释放 { delete i; i = nullptr; cout << "Here Str actual delete" << endl; } cout << "Here Str delete" << endl; }private: int *i;};class StrVec{public: StrVec():elements(nullptr),first(nullptr),cap(nullptr) { } StrVec(const StrVec & p) { auto t = alloc_n_copy(p.begin(), p.end()); elements = t.first; first = cap = t.second; } void push_back(const Str &str) { chk_n_size(); alloc.construct(first++, str); } StrVec &operator=(const StrVec &p) { auto t = alloc_n_copy(p.begin(), p.end());//防止自拷贝 free(); elements = t.first; first = cap = t.second; return *this; } Str * begin()const { return elements; } Str * end()const { return first; } ~StrVec() { free(); } std::size_t size() { return std::distance(elements, first); } std::size_t capacity() { return std::distance(elements, cap); }private: std::pair<Str *, Str *> alloc_n_copy(const Str *b, const Str *e) { auto data = alloc.allocate(e - b); return{ data, std::uninitialized_copy(b, e, data) }; } void free() { if (elements) //确保 elements不为空 { for (auto p = first; p != elements;) { alloc.destroy(--p); } alloc.deallocate(elements,cap-elements); } } void reallocate() //移动构造函数 { auto newcapacity = size() ? 2 * size() : 1; auto newdata = alloc.allocate(newcapacity); auto dest = newdata; auto elem = elements; for (std::size_t i = 0; i != size(); i++) { alloc.construct(dest++, std::move(*elem++)); //********这种方式是以移动构造的方式创建对象 // alloc.construct(dest++, *elem++);//*******这种是直接拷贝赋值的方式创建对象 } free(); elements = newdata; first = dest; cap = elements + newcapacity; } void chk_n_size() { if (size() == capacity()) { std::cout << "Here call reallocate" << endl; reallocate(); std::cout << "Here reallocate finished" << endl; } } static std::allocator<Str> alloc; Str *elements; Str *first; Str *cap;};std::allocator<Str> StrVec::alloc;int main(){ Str data1{1}; Str data2{2}; StrVec s1; std::cout << "============" << std::endl; s1.push_back(data1); std::cout << "============" << std::endl; s1.push_back(data2); std::cout << "============" << std::endl;}
可以看出移动构造函数由于保留了原来Str的int指针成员,并将原Str的int*成员设置为nullptr,所以析构的时候,没有进入if的条件语句内部
只有当类没有定义任何自己版本的拷贝控制成员,并且它的所有数据成员都能移动构造或者移动赋值时,编译器才会为它合成移动构造函数或者移动赋值运算符。
类成员的移动构造函数是删除的或者(没有自己的移动构造函数且编译器不能为其合成时),则该类的移动构造函数是删除的。
没有析构函数,也就没有移动构造函数
如果有成员是引用或者const成员,则移动赋值运算符也是删除的。
如果类定义了移动构造函数或者移动赋值运算符, 则编译器不会再为该类合成拷贝构造函数或拷贝赋值运算符。换句话说,定义了移动构造函数,必须定义自己的拷贝构造函数或者拷贝赋值运算符。
如果一个类定义了拷贝构造函数,但没有定义移动构造函数,则其对象是通过拷贝构造函数来完成移动操作的
{public: A(){} A(A&) { cout << "111" << endl; }};int main(){ A s; A s2 = std::move(s);}
区分移动和拷贝的重载函数通常是一个版本接受一个const T&, 而另一个版本接受T&&.
参数列表后面添加引用限定符。一个函数可以同时用const 和& 限定,在此情况下,引用必须在const 之后。如果有多个同名函数,只要有一个有引用限定符,则其他函数必须也需要引用限定符。左值是const &, 右值是&&。
class A{public: A &operator=(const A &)&;//只能向可修改的左值赋值 A sorted()const &; A sorted()&&;//有引用限定符,因此上面的左值sort也需要有引用private: std::vector<int> data;};A A::sorted()const &{ A ret(*this); sort(ret.data.begin(),ret.data.end()); return ret;}A A::sorted()&&{ std::sort(data.begin(), data.end()); return *this;}
移动迭代器
移动迭代器生成右值引用。可以通过标准库std::make_move_iterator()来实现
右值引用
左值右值:左值具有持久的状态,而右值要么是字面值,要么是表达式求值过程中创建的临时对象。
std::move(rr1)//调用move函数意味着除了对rr1 销毁或者重新赋值,将不再使用它
- C++ primer 薄片系列之移动构造
- C++primer薄片系列之OOP
- C++primer 薄片系列之模板
- C++primer 薄片化系列之标准库特殊实施
- C++primer薄片系列之特殊工具与技术
- C++ primer 薄片系列之 智能指针
- C++ primer 薄片系列之拷贝控制
- C++ primer 薄片系列之 STL 容器和迭代器
- C++ primer 薄片系列之 map 三两事
- C++ primer 薄片系列之重载运算符
- C++ primer 薄片系列之大型程序的工具
- C++: 移动构造函数
- C Primer Plus 学习笔记系列之(一)
- C Primer Plus 学习笔记系列之(三)
- C Primer Plus 学习笔记系列之(二)
- 《C Primer Plus》 学习笔记系列之(一)
- 《C Primer Plus》 学习笔记系列之(二)
- 《C Primer Plus》 学习笔记系列之(三)
- Docker下,pinpoint环境搭建
- vr的入门
- LuoguP3908
- JAVA作业
- springboot集成mybatis
- C++ primer 薄片系列之移动构造
- Qt分割窗口QSplitter类
- win7下创建mac虚拟机
- 习题5.2
- p(3.1-2)
- cookie与session
- 初次尝试swagger springmvc集成 生成restful api文档
- decode() missing 1 required positional arguement: 'output'
- WebViewJavascriptBridge源码解析 -1