C++ Copy Elision

时间:2021-08-02 01:24:11

故事得从 copy/move constructor 说起:

The default constructor (12.1), copy constructor and copy assignment operator (12.8), move constructor and move assignment operator (12.8), and destructor (12.4) are special member functions. [ Note: The implementation will implicitly declare these member functions for some class types when the program does not explicitly declare them. The implementation will implicitly define them if they are odr-used (3.2). See 12.1, 12.4 and 12.8. — end note ]

上面这段文字来自 C++11 Standard 中 “12 Special member functions”,关于什么时候使用 copy/move constructor 什么时候使用 copy/move assignment operator,在 “12.8 Copying and moving class objects” 中第一段有详细的说明:

A class object can be copied or moved in two ways: by initialization (12.1, 8.5), including for function argument passing (5.2.2) and for function value return (6.6.3); and by assignment (5.17). Conceptually, these two operations are implemented by a copy/move constructor (12.1) and copy/move assignment operator (13.5.3).

也就是说:在初始化、函数参数传递和函数值返回的时候将会使用到 copy/move constructor,而在赋值的时候才会使用到 copy/move assignment operator

一、copy/move constructor

A non-template constructor for class X is a copy constructor if its first parameter is of type X&, const X&, volatile X& or const volatile X&, and either there are no other parameters or else all other parameters have default arguments (8.3.6).

Xcopy constructor 是一个非模板构造函数,该函数的第一个参数必须是 X&const X&volatile X& 或者 const volatile X&,如果还有其他参数,其他参数必须有默认值

为了简化问题,这里我们不讨论 move constructor,也就是说,假设 gcc 版本为 4.1.2 并且不支持 C++0x 提出的 move semantic。看看下面这段例子:

#include <iostream>

struct X {
X() { std::cout << "default constructor" << std::endl; }
~X() { std::cout << "destructor" << std::endl; } X(const X&) { std::cout << "copy constructor\n"; }
X& operator=(const X&) { std::cout << "copy assignment operator\n"; }
}; int main() {
X a; // initialization, use default constructor
X aa(a); // initialization, use copy constructor
X aaa = a; // initialization, use copy constructor
aa = a; // assignment, use copy assignment operator
return 0;
}

按 Standard 所说,上面代码的行为应该是和注释一样,于是我们编译并运行试试:

$ g++ a.cpp -o a
$ ./a
default constructor
copy constructor
copy constructor
copy assignment operator
destructor
destructor
destructor

结果的确是和预期的一致,那么再来看看需要使用 copy constructor 的另外一种情况 “function value return”,这里不涉及利用函数返回值初始化另一个对象的情况,只是单纯的调用函数:

#include <iostream>

struct X {
X() { std::cout << "default constructor" << std::endl; }
~X() { std::cout << "destructor" << std::endl; } X(const X&) { std::cout << "copy constructor\n"; }
X& operator=(const X&) { std::cout << "copy assignment operator\n"; }
}; X f() {
return X();
} int main() {
f();
return 0;
}

编译并运行,其结果如下:

$ g++ a.cpp -o a
$ ./a
default constructor
destructor

预期的那次 copy constructor 调用并没有出现。这里不得不说到一个编译器优化:return value optimization

二、return value optimization

When certain criteria are met, an implementation is allowed to omit the copy/move construction of a class object, even if the constructor selected for the copy/move operation and/or the destructor for the object have side effects. In such cases, the implementation treats the source and target of the omitted copy/move operation as simply two different ways of referring to the same object, and the destruction of that object occurs at the later of the times when the two objects would have been destroyed without the optimization.126 This elision of copy/move operations, called copy elision, is permitted in the following circumstances (which may be combined to eliminate multiple copies):

— in a return statement in a function with a class return type, when the expression is the name of a non-volatile automatic object (other than a function or catch-clause parameter) with the same cv-unqualified type as the function return type, the copy/move operation can be omitted by constructing the automatic object directly into the function’s return value

— in a throw-expression, when the operand is the name of a non-volatile automatic object (other than a function or catch-clause parameter) whose scope does not extend beyond the end of the innermost enclosing try-block (if there is one), the copy/move operation from the operand to the exception object (15.1) can be omitted by constructing the automatic object directly into the exception object

— when a temporary class object that has not been bound to a reference (12.2) would be copied/moved to a class object with the same cv-unqualified type, the copy/move operation can be omitted by constructing the temporary object directly into the target of the omitted copy/move

— when the exception-declaration of an exception handler (Clause 15) declares an object of the same type (except for cv-qualification) as the exception object (15.1), the copy/move operation can be omitted by treating the exception-declaration as an alias for the exception object if the meaning of the program will be unchanged except for the execution of constructors and destructors for the object declared by the exception-declaration.

这里注意第一段就行了:某些场景下,编译器可以省略一次 copy/move construction,不管有没有 side effect。省略 copy/move construction 能带来什么 side effect 呢?copy/move constructor 里的代码不会执行(比如上面例子中的 cout 信息)。Standard 给出几个省略 copy/move construction 的场景,场景一就是上面的情况:

函数的 return 语句中的表达式是一个非 volatile 对象的名字,并且其 cv-unqualified type 和函数返回值的 cv-unqualified type 相同,此时可以省略一次 copy/move construction。

那么什么是 cv-unqualified type 和 cv-qualified type 呢?如果有耐心的话,Standard 里也是有讲的,在第 3.9.3 节,这里我就不贴原文了,简单的说就是:"cv" 分别指的是 "const" 和 "volatile",cv-unqualified type 指的是没有这两个修饰符修饰的类型。可以看看 Stack Overflow 上面的解释,简单精确:What does "cv-unqualified" mean in C++?

现在就能够解释上面例子中的行为了:函数返回值被保存到了一个临时变量里,而构造这个临时变量调用的是类 X 的 copy/move constructor,传入的参数是 return 语句后面的表达式,巧合的是,临时变量和传入参数的 cv-unqualified type 相同,因此 gcc 把这次 copy/move construction 省略掉了(或者说优化掉了),而在 copy/move constructor 中的 cout 代码就自然不被执行了,这就是省略 copy/move construction 带来的 side effect 吧。

gcc 提供了一个编译选项:-fno-elide-constructors,用它能够关闭 gcc 省略 copy/move construction 的默认行为,所以,如果我们这样编译代码并运行的话,就能够看见期望看见的那次 copy/move construction 了:

$ g++ a.cpp -o a -fno-elide-constructors
$ ./a
default constructor
copy constructor
destructor
destructor

关于 return value optimization,可以看看*:Return value optimization

三、Copy Elision

让我们更近一步,如果细心的话可能已经发现,在上面贴出来的省略 copy/move construction 的条件里还有这么一条:

— when a temporary class object that has not been bound to a reference (12.2) would be copied/moved to a class object with the same cv-unqualified type, the copy/move operation can be omitted by constructing the temporary object directly into the target of the omitted copy/move

当用一个临时对象初始化另一个对象的时候,如果他们俩的 cv-unqualified type 相同,并且临时对象没有和任何引用绑定,那么此次 copy/move construction 也是可以省略的:

#include <iostream>

struct X {
X() { std::cout << "default constructor" << std::endl; }
~X() { std::cout << "destructor" << std::endl; } X(const X&) { std::cout << "copy constructor\n"; }
X& operator=(const X&) { std::cout << "copy assignment operator\n"; }
}; X f() {
return X();
} int main() {
X a = f();
return 0;
}

如果我们直接编译运行的话,那两次 copy/move construction 肯定都被优化掉了:

$ g++ a.cpp -o a
$ ./a
default constructor
destructor

如果加上 -fno-elide-constructors 这个选项:

$ g++ a.cpp -o a -fno-elide-constructors
$ ./a
default constructor
copy constructor
destructor
copy constructor
destructor
destructor

为了作对比,如果不用那个临时变量初始化一个 X 对象,而是先把它赋值给一个 X 对象 a,然后用 a 来 copy initialize 一个 X 对象 b,那么初始化 b 的那次 copy construction 是不会被省略的:

#include <iostream>

struct X {
X() { std::cout << "default constructor" << std::endl; }
~X() { std::cout << "destructor" << std::endl; } X(const X&) { std::cout << "copy constructor\n"; }
X& operator=(const X&) { std::cout << "copy assignment operator\n"; }
}; X f() {
return X();
} int main() {
X a = f();
X b = a;
return 0;
}

编译并运行:

$ g++ a.cpp -o a
$ ./a
default constructor
copy constructor
destructor
destructor

结果和预想的一致,copy elision 大致就解释完了,有空可以看看*:Copy elision

C++ Copy Elision的更多相关文章

  1. Copy elision in C&plus;&plus;

    Copy elision (or Copy omission) is a compiler optimization technique that avoids unnecessary copying ...

  2. copy elision

    http://book.51cto.com/art/200810/93007.htm 1.2.2  数据传送指令 mov:数据移动.第一个参数是目的,第二个参数是来源.在C语言中相当于赋值号.这是最广 ...

  3. copy elison &amp&semi; RVO &amp&semi; NRVO

    蓝色的博文 To summarize, RVO is a compiler optimization technique, while std::move is just an rvalue cast ...

  4. Google C&plus;&plus; Style Guide

    Background C++ is one of the main development languages used by many of Google's open-source project ...

  5. 翻译「C&plus;&plus; Rvalue References Explained」C&plus;&plus;右值引用详解 Part6:Move语义和编译器优化

    本文为第六部分,目录请参阅概述部分:http://www.cnblogs.com/harrywong/p/cpp-rvalue-references-explained-introduction.ht ...

  6. move和转发

    总的来说C++09跟C++98相比的变化是极其重大的.这个变化体现在三个方面,一个是形式上的变化,即在编码形式层面的支持,也就是对应我们所谓的编程范式(paradigm).C++09不会引入新的编程范 ...

  7. C&plus;&plus;临时对象以及针对其进行的优化

    C++临时对象以及针对其进行的优化 C++中真正的临时对象是看不见的,它们不出现在你的源代码中. 那么什么时候回产生临时对象呢?主要是三个时刻: 产生临时对象的三个时刻: 用构造函数作为隐式类型转换函 ...

  8. Google C&plus;&plus; 代码规范

    Google C++ Style Guide   Table of Contents Header Files Self-contained Headers The #define Guard For ...

  9. C&plus;&plus; Standards Support in GCC - GCC 对 C&plus;&plus; 标准的支持

    C++ Standards Support in GCC - 2019-2-20 GCC supports different dialects of C++, corresponding to th ...

随机推荐

  1. Otsu algorithm

    一.介绍 OTSU算法也称最大类间差法,有时也称之为大津算法,被认为是图像分割中阈值选取的最佳算法,计算简单,不受图像亮度和对比度的影响,因此在数字图像处理上得到了广泛的应用.它是按图像的灰度特性,将 ...

  2. ViewState

    ViewState就像一个记录本,由于WebFormd的无状态性,刷新了页面.那么这个页面就和上一个页面没有任何关系了.为了使刷新前的页面和本页面产生联系,ViewState的作用就是记录刷新前页面的 ...

  3. Moq的使用

    参考资料: 1. http://www.codeproject.com/Tips/729646/TDD-using-MOQ 2. https://github.com/Moq/moq4/wiki/Qu ...

  4. 1&period;4&period;8 拼凑在一起&lpar;putting the pieces together&rpar;

    putting the pieces together 在最高的级别,schema.xml结构如下, <schema> <types> <fields> <u ...

  5. Connection open error &period; Connection Timeout Expired&period; The timeout period elapsed during the post-login phase&period;

    是这样的,最近我在开发Api(重构),用的数据库是Sqlserver,使用的Orm是 SqlSugar(别问我为什么选这个,boss选的同时我也想支持国人写的东西,且文档也很全). 被催的是,写好了程 ...

  6. python 线程&sol;线程锁&sol;信号量

    单线程 #常规写法 import threading import time def sayhi(num): # 定义每个线程要运行的函数 print("running on number: ...

  7. cocos2d-x 绘制图形

    转载请注明出处:http://blog.csdn.net/oyangyufu/article/details/25841727 绘制图形例如以下:   程序代码: 须要又一次定义父类虚函数draw() ...

  8. C&num;:导入Excel通用类(Xls格式)

    PS:在CSV格式和XLSX格式中有写到通用调用的接口和引用的插件,所以在这个xls格式里面并没有那么详细,只是配上xls通用类. 一.引用插件NPOI.dll.NPOI.OOXML.dll.NPOI ...

  9. MSMQ消息传递的优先级

    一.消息传递的优先级 在MSMQ中消息在队列里传输是分有优先级的,这里我就以实例的形式介绍下关于优先级的使用,优先级一共有七种,MessagePriority枚举里全部进行了封装.因这里只作程序演示就 ...

  10. 解题:USACO07FEB The Cow Lexicon

    题面 第一次做Trie上dp,感谢 @i207M 的资瓷 对子串们建立一棵Trie,设$dp[i][j]$表示到母串第$i$位为止在$Trie$上的$j$号节点时的最小修改数量,然后就可以枚举母串各位 ...