C++ 智能指针(1.5):move 语义

C++智能指针(1.5):move语义

move语义

定义

右值引用(Rvalue Referene)是 C++ 11中引入的新特性,它实现了转移语义(Move Sementics)和精确传递(Perfect Forwarding),其主要目的有

  • 消除两个对象交互时不必要的对象拷贝,节省运算存储资源,提高效率。
  • 能够更简洁明确地定义泛型函数。

实现

move语义的实现如下

template <class _Tp>
typename remove_reference<_Tp>::type&&
move(_Tp&& __t) _NOEXCEPT
{
    typedef typename remove_reference<_Tp>::type _Up;
    return static_cast<_Up&&>(__t);
}

可以看到其实move语义的实现非常简单,它将传入的参数(__t)强制类型转换(static_cast)成了对应类型的右值(<_Up&&>)。也就是说,使用move语义之后,编译器窃取(一般会在移动构造函数和移动赋值操作符里将原有对象指向nullptr)了原有对象的右值,并延长了这个右值的生命周期,而没有对右值做任何拷贝操作,这个右值将被用来赋值给其他的对象。

测试

定义一个obj类

class Obj {
public:
    Obj() { std::cout << "Construct" << std::endl; }

    Obj(const Obj &other) { std::cout << "Copy" << std::endl; }

    Obj(Obj &&other) noexcept { std::cout << "Move" << std::endl; }

    ~Obj() { std::cout << "Destruct" << std::endl; }

    void Print() { std::cout << "Print" << std::endl; }
};

定义一个Function函数,使用move语义返回

Obj Function() {
    Obj obj;
    return move(obj);
}

int main() {
    Obj obj = Function();
    return 0;
}
/*
output:
Construct
Move
Destruct
Destruct
*/

可以看到在Function函数返回右值后,obj对象调用了移动构造函数,而不是拷贝构造函数。

RVO (return value optimisation)

RVO(返回值优化)是一种编译器优化技术,允许编译器在调用点(call site)直接构造函数的返回值。

在Function函数中返回一个Obj对象

Obj Function() {
    Obj obj;
    return obj; // NRVO (named return value optimisation)
}

int main() {
    Obj obj = Function();

    return 0;
}
/*
output:
Construct
Destruct
 */

原本Function函数在返回时应该会进行一次拷贝,然而调试结果却告诉我们obj只在Function函数中被构造了一次,在程序结束时被析构了一次。这是因为编译器使用了RVO机制。这里Function函数返回的是一个左值,所以又称NRVO(命名返回值优化)。这项技术在C++ 11里被称为Copy Elision(拷贝省略)。

如果我们在返回时使用move语义

Obj Function() {
    Obj obj;
    return move(obj);
}

int main() {
    Obj obj = Function();
    return 0;
}
/*
output:
Construct
Move
Destruct
Destruct
*/

可以看到,因为使用了move语义,函数实际返回的是一个右值引用(Obj&&),而不是函数定义中的对象(Obj),没有触发RVO。也就是说,要触发RVO机制,必须保证函数实际的返回值类型和函数定义中的返回值类型一致。

如果我们把函数返回值类型改为右值引用

Obj &&Function() {
    Obj obj;
    return move(obj);
}

int main() {
    Obj obj = Function();
    return 0;
}
/*
output:
Construct
Destruct
Move
Destruct
*/

可以看到main函数中的obj对象使用了移动构造函数,触发了RVO机制。

现在对函数稍加修改,再次运行

Obj Function(bool flag) {
    Obj obj1, obj2;
    if(flag)
        return obj1;
    return obj2;
}

int main() {
    Obj obj = Function(true);
    return 0;
}
/*
output:
Construct
Construct
Move
Destruct
Destruct
Destruct
*/

这一次Function函数使用了两次默认构造函数构造了两个Obj对象,返回时又调用了一次移动构造函数(左值返回时编译器会优先使用移动构造函数,不支持移动构造时才调用拷贝构造函数),没有触发RVO机制。这是因为编译器使用了parent stack frame(父堆栈帧)来避免返回值拷贝,但如果在返回时使用了判断语句,编译器在编译时将不能确定将哪一个作为返回值,

参考

RVO V.S. std::move

右值引用与转移语义

comments powered by Disqus