C++ copy elision

c++中的copy elision是指一种编译器行为,即尽可能的消除不必要的拷贝构造和移动构造(当类可移动时则消除移动,不可移动时消除拷贝),包含以下几种情况:

  • RVO (Return Value Optimization):当return之后的表达式为纯右值时,直接在函数调用的位置去构造返回值对象,省略掉拷贝/移动。  该行为在c++17视作编译器的优化,可以通过编译选项禁用掉;在c++17之后成为标准而非优化,即编译器在任何情况下都必须这么做,无法通过编译选项来禁用。
  • NRVO (Named Return Value Optimization):当return之后的表达式为左值时,且该左值为该函数栈上的变量,则直接在函数调用的位置去构造返回值对象,省略掉拷贝/移动。 该行为是优化行为,可以通过编译选项来禁用。
  • 初始化变量时,当初始化表达式为纯右值,则执行copy elision。

下面举个具体栗子:

#include <iostream>

struct A {
    A() {
        printf("%p ctor\n", this);
    }

    A(const A &) {
        printf("%p copy ctor\n", this);
    }

    A(A &&) {
        printf("%p move ctor\n", this);
    }

    ~A() {
        printf("%p dtor\n", this);
    }
};

A f_RVO() {
    return A();
}

A f_NRVO() {
    A a;
    printf("%p in func\n", &a);
    return a;
}

int main() {
    // A a = f_RVO();
    A a = f_NRVO();
    printf("%p in main\n", &a);
    return 0;
}

我们写了一个可移动的类A,在构造时打印出它的地址;写了一个f_RVO()函数、一个f_NRVO()函数,也打印出相应的对象地址;用于演示copy elision在内存中是如何运作的。

① c++11,用-fno-elide-constructors禁用掉copy elision,观察f_RVO()函数:

0000005456dffa4f ctor
0000005456dffa9f move ctor
0000005456dffa4f dtor
0000005456dffa9e move ctor
0000005456dffa9f dtor
0000005456dffa9e in main
0000005456dffa9e dtor

输出如上  (提示:栈空间由高地址向低地址生长)

我们可以发现,首先在f_RVO()函数栈上(a4f地址)构造了一个对象,然后在main函数栈上开辟了一块临时地址(a9f)执行移动构造,再在main函数栈上真正要构造a的位置(a9e)执行移动构造。

共执行了1次构造+2次移动构造 (若类A不可移动则是1次构造+2次拷贝构造)。

② c++11,用-fno-elide-constructors禁用掉copy elision,观察f_NRVO()函数:

000000d6451ffa5f ctor
000000d6451ffa5f in func
000000d6451ffaaf move ctor
000000d6451ffa5f dtor
000000d6451ffaae move ctor
000000d6451ffaaf dtor
000000d6451ffaae in main
000000d6451ffaae dtor

现象基本同上。先在f_NRVO()函数栈上(a5f地址)构造了一个对象,然后在main函数栈上开辟了一块临时地址(aaf)执行移动构造,再在main函数栈上真正要构造a的位置(aae)执行移动构造。

③ c++17,用-fno-elide-constructors禁用掉copy elision,观察f_RVO()函数:

000000cd59dffc0f ctor
000000cd59dffc0f in main
000000cd59dffc0f dtor

由于在c++17中RVO是必须的,所以-fno-elide-constructors不管用了,直接在main函数栈上原地构造了。

④ c++17,用-fno-elide-constructors禁用掉copy elision,观察f_NRVO()函数:

000000bf551ff92f ctor
000000bf551ff92f in func
000000bf551ff97f move ctor
000000bf551ff92f dtor
000000bf551ff97f in main
000000bf551ff97f dtor

与c++11不同的是,在main函数栈上会少一次移动构造,不再有临时位置去构造一个临时对象了。

⑤ 开启优化,则原地构造。

000000fe2f3ff6ff ctor
000000fe2f3ff6ff in main
000000fe2f3ff6ff dtor

小测验:以下写法推荐吗?有什么问题?

vector<int> f() {
    vector<int> v;
    return std::move(v);
}

答案:

会阻止编译器做copy elision,因为std::move表达式是将亡值,它既不是左值也不是纯右值,所以既不能进行RVO也不能进行NRVO。 这种情况下就直接return v就好了,不要自作主张的加个std::move上去。。。

附栗子的全部结果

c++11c++11 -fno-elide-constructorsc++17c++17 -fno-elide-constructors
f_RVO()000000fe2f3ff6ff ctor
000000fe2f3ff6ff in main
000000fe2f3ff6ff dtor
0000005456dffa4f ctor
0000005456dffa9f move ctor
0000005456dffa4f dtor
0000005456dffa9e move ctor
0000005456dffa9f dtor
0000005456dffa9e in main
0000005456dffa9e dtor
0000000365fffbff ctor
0000000365fffbff in main
0000000365fffbff dtor
000000cd59dffc0f ctor
000000cd59dffc0f in main
000000cd59dffc0f dtor
f_NRVO()000000f72b1ff95f ctor
000000f72b1ff95f in func
000000f72b1ff95f in main
000000f72b1ff95f dtor
000000d6451ffa5f ctor
000000d6451ffa5f in func
000000d6451ffaaf move ctor
000000d6451ffa5f dtor
000000d6451ffaae move ctor
000000d6451ffaaf dtor
000000d6451ffaae in main
000000d6451ffaae dtor
000000ec881ffdff ctor
000000ec881ffdff in func
000000ec881ffdff in main
000000ec881ffdff dtor
000000bf551ff92f ctor
000000bf551ff92f in func
000000bf551ff97f move ctor
000000bf551ff92f dtor
000000bf551ff97f in main
000000bf551ff97f dtor
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值