C++ primer 薄片系列之移动构造

移动构造函数

为啥要有移动构造函数, 个人浅见,为避免调用成员的复制构造函数造成额外的开销,毕竟复制指针要来的更方便快捷,因而产生的。比如一个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 销毁或者重新赋值,将不再使用它

猜你喜欢

转载自blog.csdn.net/jxhaha/article/details/78448569