0
点赞
收藏
分享

微信扫一扫

C++11:移动语义

RIOChing 2022-04-29 阅读 57
c++

        一个带指针的类拷贝构造函数没使用深拷贝,浅拷贝会导致野指针的问题直接指向析构的一块区域导致出问题
        用深拷贝解决这个问题:(必须传引用)如果我们实现拷贝构造函数的时候,不传引用传值,会引起拷贝的死循环,拷贝的时候会把我们的对象传给拷贝构造函数,因为会生成一个临时对象,又会把实参拷贝给这个临时对象,这个过程又会重复调用拷贝构造函数,就会导致死循环

A getA(){
    A a;
    return a;
}

A x = getA();

 拿到getA的返回值的时候把它复制给x这个过程实际上是在初始化x,实际是在调用拷贝构造函数。 getA内部会创建A对象,这个过程会调用A的默认构造函数,再把A返回,就是一个右值,用返回值去初始化x,getA的返回值的对象就会被销毁掉。
        这个过程有可以优化的地方:能不能不把A对象销毁掉,直接把x指针指向A对象中的内存?
直接不拷贝,占据A的内存,A的内存因为x而续命,生命周期增加了也就是移动语义。
        这个过程就减小了内存的分配和拷贝,直接占用返回的对象的内存

移动构造函数

A(A&& b):m_ptr(b.m_ptr)
{
    cout<<"move construct"<<endl;
    b.m_ptr = nullptr;
}

传入的就是将亡值,移动构造函数实现是和浅拷贝十分类似的,就是个浅拷贝,就是浅拷贝的对象必须是右值,或是将亡值,而不是左值。

直接指向了b中分配的内存,没有了拷贝分配内存getA返回的 刚好是将亡值,就会调用移动构造函数避免了拷贝

std::move

std::move函数就是将左值转换为右值

作用:避免不必要的拷贝操作(直接调用移动构造函数,转移资源),提高性能,本身并没有移动任何数据 
只有调用了std::move() 这个左值就不能用了,再访问被移动构造过得值 就会出问题。

int val = 123;
int && y = std::move(val);

std::forward和std::move

std::move是无条件转换,不管它的参数是左值还是右值,都会被强制转换成右值,不move任何东西
std::forward是有条件转换,只有在他的参数绑定到一个右值时,他才转换它的参数到一个右值。当参数绑定到左值时,转换后还是左值
对右值引用 std::move,对universal引用则使用std::forward
std::move和std::forward在运行期都没做任何事情

拷贝赋值函数与移动赋值函数

拷贝赋值函数:把被复制的数据拷贝到目标对象中
移动赋值函数:直接用当前对象的指针去占有目标中的内存(也是适合右值和将亡值的情况)

A& operator= (const A& a){    //拷贝复制函数
    m_ptr = new int(*a.m_ptr);
    cout<<"copy operator"<< endl;
    return *this;
}

A& operator= (A && a){    //移动复制函数
    m_ptr= a.m_ptr;
    a.m_ptr = nullptr;
    cout<<"move operator"<< endl;
    return *this;
}

noexcept

        移动构造函数抛出异常是很危险的,因为移动语义还没有完成,抛出异常会导致一些指针成为悬空指针,一次尽量编写不抛出异常的移动构造函数,通过添加noexcept关键字(在函数后面加上noexcept,代表这个函数不会跑出异常,如果抛出异常就会终止)可以保证移动构造函数抛出异常会直接调用terminate 终止程序运行。不抛出异常的移动构造函数和移动赋值运算符必须标记位noexcept。还可以使用std::move _if_noexcept代替std_move,在没有noexcept关键字时候,返回一个左值引用进行拷贝;在有noexcept时,返回一个右值引用实现移动语义

A& operator= (A && a) noexcept {    //移动复制函数
    m_ptr= a.m_ptr;
    a.m_ptr = nullptr;
    cout<<"move operator"<< endl;
    return *this;
}

​A(A&& b) noexcept :m_ptr(b.m_ptr)
{
    cout<<"move construct"<<endl;
    b.m_ptr = nullptr;
}

C++某些库在遇到没有加 noexcept 的移动构造或是移动复制会拒绝调用移动复制和移动构造,继续调用原来的拷贝构造和拷贝复制
之前编译器是可以使用 RVO 优化来实现一样效果

vector移动语义

int main()
{
    vector<A>vec(1);
    A a(5);
    vec.push_back(std::move(a));
    return 0;
}
  1. 第1个默认构造函数是因为vector<A>vec(1),所以事先使用默认构造函数构造了一个A对象
  2. 第2个默认构造函数是因为A  a  使用默认构造函数构造了一个对象
  3. 第3个移动构造函数是由于vector重新分配内存而导致的,vec初始的容量只有1,且里面已经有一个对象了,就是vector<A>vec(1)的时候创建的,所以再向vec里面添加A对象时,就会导致vec重新分配内存,由于vec中对象定义了移动偶造函数且是可用的(因为声明为了 noexcept ),所以就会调用移动构造函数将vec中原始的那个对象移动到新的内存中,从而输出移动构造函数
  4. 第4个移动构造函数才是因为A对象 a被移动到vector对象vec新的空间而输出的
举报

相关推荐

0 条评论