移动构造函数

左值(lvalue, left value),顾名思义就是赋值符号左边的值。准确来说, 左值是表达式(不一定是赋值表达式)后依然存在的持久对象。
右值(rvalue, right value),右边的值,是指表达式结束后就不再存在的临时对象。
C++11 中为了引入强大的右值引用,将右值的概念进行了进一步的划分,分为:纯右值和将亡值。
纯右值 (prvalue, pure rvalue),纯粹的右值,要么是纯粹的字面量,例如 10, true; 要么是求值结果相当于字面量或匿名临时对象,例如 1+2。非引用返回的临时变量、运算表达式产生的临时变量、原始字面量、Lambda 表达式都属于纯右值。

不存在移动构造时

如下代码,在一个函数里返回一个类的实例对象.这种情况在许多代码中都经常存在.

#include <iostream>

#include <chrono>

using namespace std;

class TestCLass
{
public:
    TestCLass() { cerr << __PRETTY_FUNCTION__ << endl; }
    TestCLass(const TestCLass &) { cerr << __PRETTY_FUNCTION__ << endl; }
    TestCLass(TestCLass &&) { cerr << __PRETTY_FUNCTION__ << endl; }
    TestCLass &operator=(const TestCLass &)
    {
        cerr << __PRETTY_FUNCTION__ << endl;
        return *this;
    }
    TestCLass &operator=(TestCLass &&)
    {
        cerr << __PRETTY_FUNCTION__ << endl;
        return *this;
    }
};

TestCLass test()
{
    return TestCLass();
}

int main()
{
    test();
    return 0;
}

讲上述代码 main 函数部分改成如下

int main()
{
    auto a = test();
    return 0;
}

如上: 当 test 返回时将调用一次构造函数做为返回值A参数, 紧接着根据这个返回值A参数,构造一个临时对象B. 这个B就是实际上的返回值. 也就是说 return TestClass(); 实际上调用用了两次构造函数,一次是默认构造,一次是拷贝构造.
问题:如上可以发现,两次构造的对象实际上并没有被使用,仅在被拷贝后就析构掉了!!! 同样的对象被重复构造了两次!!! 那么如果我们能够重复使用对象的内存空间,仅构造一次,将会减少很多类似的重复动作带来的开销!!!

添加移动构造函数后:

移动构造函数的作用

#include <iostream>

using namespace std;

class Base
{
public:
    Base(void) { cout << __PRETTY_FUNCTION__ << ":" << this << endl; }
    Base(const Base &) { cout << __PRETTY_FUNCTION__ << ":" << this << endl; }
    Base(Base &&) { cout << __PRETTY_FUNCTION__ << ":" << this << endl; }
    // 上面两个 Base(const Base &) 与 Base(Base &&) 可用下面模板代替
    // template<typename T>
    // Base(T &&) { cout << __PRETTY_FUNCTION__ << ":" << this << endl; }

    Base &operator=(const Base &)
    {
        cout << __PRETTY_FUNCTION__ << ":" << this << endl;
        return *this;
    }

    ~Base() { cout << __PRETTY_FUNCTION__ << ":" << this << endl; }

private:
};

Base func()
{
    return Base();
}

int main(int argc, char **argv)
{
    auto bb = func();

    return 0;
}

注意,目前多数编译器都会对程序中发生的拷贝操作进行优化,因此直接编译运行此程序时,看到的往往是优化后的输出结果:

在 Linux 上使用 g++ demo.cpp -fno-elide-constructors,就可以看到完整的输出结果:

可以看出函数 func 在没有优化时候流程是这样的:

  • 执行Base() 默认构造函数,构造一个临时的匿名对象
  • 拷贝匿名对象到返回值,并销毁匿名变量
  • 执行 bb = 返回值,也就是执行拷贝构造函数,然后销毁返回值
    这之间多次调用了构造函数。

移动构造函数

      ...
    Base(Base &&) { cout << __PRETTY_FUNCTION__ << ":" << this << endl; }
      ...


可以看出,添加移动构造函数后执行了移动构造函数Base::Base(Base&&)。减少了多次调用构造函数的开销。(移动语义与完美转发仅在编译阶段起作用,运行期间不执行任何代码)。

移动语义与完美转发

C++11朝码夕解: move和forward
C++11 std::move和std::forward
c++11 中的 move 与 forward
[CPP] 左值 lvalue,右值 rvalue 和移动语义 std::move
[c++11]我理解的右值引用、移动语义和完美转发

posted @ 2020-12-22 13:37  sinpo828  阅读(655)  评论(0编辑  收藏  举报