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的条件语句内部
执行结果如下,可以看出移动构造函数由于保留了原来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 销毁或者重新赋值,将不再使用它

原创粉丝点击