故事得从 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++

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

  2. C++的Copy Elision导致的奇怪问题

    最近写设计模式作业的时候, 有一个作业是实现装饰器模式 (Decorator Pattern), 由于我不会 Java, 所以只能用 C++ 来实现 在这个背景下, 会有简单(表意)的几个类, 如下: ...

  3. copy elision

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

  4. copy elison & RVO & NRVO

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

  5. Google C++ Style Guide

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

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

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

  7. move和转发

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

  8. C++临时对象以及针对其进行的优化

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

  9. Google C++ 代码规范

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

随机推荐

  1. js 函数提升和变量提升

    总结: 函数提升比变量提升优先级高! 词法分析 词法分析方法: js运行前有一个类似编译的过程即词法分析,词法分析主要有三个步骤: 分析参数 再分析变量的声明 分析函数说明 具体步骤如下: 函数在运行 ...

  2. python decorator simple example

    Why we need the decorator in python ? Let's see a example: #!/usr/bin/python def fun_1(x): return x* ...

  3. paip.java c++得到当前类,方法名称以及行号

    paip.java c++得到当前类,方法名称以及行号 作者Attilax 艾龙,  EMAIL:1466519819@qq.com 来源:attilax的专栏 地址:http://blog.csdn ...

  4. javaweb回顾第六篇谈一谈Servlet线程安全问题

    前言:前面说了很多关于Servlet的一些基础知识,这一篇主要说一下关于Servlet的线程安全问题. 1:多线程的Servlet模型 要想弄清Servlet线程安全我们必须先要明白Servlet实例 ...

  5. HTML5 <a>标签的ping属性用法

    随着移动互联网用户井喷式的增长,web前端开发中的HTML5在近几年备受瞩目,越来越多的人从事html5开发相关工作.今天小编也来凑个热闹,和大家一起来谈谈HTML5中<a>标签的ping ...

  6. Top 20 Java Libries Used by Github's Most Popular Java Projects

    Top 20 Java Libries Used by Github's Most Popular Java Projects:

  7. React同构直出原理浅析

    通常,当客户端请求一个包含React组件页面的时候,服务端首先响应输出这个页面,客户端和服务端有了第一次交互.然后,如果加载组件的过程需要向服务端发出Ajax请求等,客户端和服务端又进行了一次交互,这 ...

  8. ES5 数组方法reduce

    reduce() 方法接收一个函数作为累加器(accumulator),数组中的每个值(从左到右)开始合并,最终为一个值. 参数 callback 执行数组中每个值的函数,包含四个参数 previou ...

  9. What is a Statistic?

    from: https://controls.engin.umich.edu/wiki/index.php/Basic_statistics:_mean,_median,_average,_stand ...

  10. Scala 深入浅出实战经典 第48讲:Scala类型约束代码实战及其在Spark中的应用源码解析

    王家林亲授<DT大数据梦工厂>大数据实战视频 Scala 深入浅出实战经典(1-64讲)完整视频.PPT.代码下载:百度云盘:http://pan.baidu.com/s/1c0noOt6 ...