故事得从 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. Cocos2d-x3.6 Android编译问题

    在Cocod2d-x论坛上看到越来越多人吐槽新版本更新太快,改动太大,而且经常有BUG导致升级要折腾很久很久..但我就是喜欢折腾,喜欢升级到最新版本,看看有了哪些新功能,哪些改进.为此也折腾了不少,遇 ...

  2. [BTS] WCF-OracleDB

    When I insert some data to Oracle, BizTalk WCF-OracleDB throw this error. A message sent to adapter ...

  3. Java攻城狮面试考题

    <html> <head> <meta http-equiv="Content-Type" content="text/html; char ...

  4. Maven学习总结(四)——Maven核心概念--转载

    一.Maven坐标 1.1.什么是坐标? 在平面几何中坐标(x,y)可以标识平面中唯一的一点. 1.2.Maven坐标主要组成 groupId:组织标识(包名) artifactId:项目名称 ver ...

  5. 不能返回函数内部new分配的内存的引用

    以前在开发电子秤接口动态库时,曾尝试在用于获取重量的函数外面定义一个字符串指针,然后作为参数传入函数内部,然后在函数内部new,用来输出函数执行过程中发生的错误.但是总是出错,没有找到原因,后来无意中 ...

  6. [推荐]Bitnami 开源软件包安装解决方案

    [推荐]Bitnami 开源软件包安装解决方案 Bitnami is an app store for server software. Install your favorite applicati ...

  7. Bitbucket Repository size limits

    Repository size limits By Justen Stepka, Product Manager on May 30, 2014 In order to improve and mai ...

  8. [GO编程] GO入门语法基础

    学习一门语言,首先肯定是要熟悉他的语法,然后才可以进行编程开发,虽然本人使用过C++,.net等语言,不过对于GO的一些新特性还是需要多多熟悉,否则即使看得懂也写不出程序来.今天我们就开始我们的GO ...

  9. Weblogic Session复制策略与方式

    在Weblogic中,HttpSession Replication的方式是通过在weblogic.xml中的session- descriptor的定义persistent-store-type来实 ...

  10. assertThat用法

    一般匹配符1.assertThat( testedNumber, allOf( greaterThan(8), lessThan(16) ) ); 注释: allOf匹配符表明如果接下来的所有条件必须 ...