故事得从 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. thrift之TTransport层的堵塞的套接字I/O传输类TSocket

    本节将介绍第一个实现具体传输功能的类TSocket,这个类是基于TCP socket实现TTransport的接口.下面具体介绍这个类的相关函数功能实现. 1.构造函数 分析一个类的功能首先看它的定义 ...

  2. atitit.设计模式(2) -----查表模式/ command 总结

    atitit.设计模式(2) -----查表模式/ command 总结 1. 应用场景: 1 1. 取代一瓦if else 1 2. 建设api rpc风格的时候儿. 1 3. 菜单是Command ...

  3. atitit 提升数据库死锁处理总结

    atitit 提升数据库死锁处理总结 正常的来说,锁上都是自动的..不用官.. 正常来讲,insert时不需要加rowlock,就默认是rowlock了, #-----锁的自动转换原理.(正常的不用理 ...

  4. paip.陕北方言 广东方言的不同单字 1千高频字

    paip.陕北方言 广东方言的不同单字 1千高频字  SELECT * FROM `canx` c where c.cn in (  select zi from hezi1k h)  and st ...

  5. Leetcode 206 Reverse Linked List 链表

    将单向链表反转 完成如图操作,依次进行即可 1 2 3 /** * Definition for singly-linked list. * struct ListNode { * int val; ...

  6. QueryTask查询结果最多500条的问题

    参考: http://dingtao-wgs.blog.163.com/blog/static/5026071420129813059865/ 上面文章中给出的解决方法:今天在做querytask的时 ...

  7. Shell获取上一个月、星期的时间范围

    #!/bin/bash date_today=`date -d '1 day ago' +%Y%m%d`   #最近7天 date_befor_7day=`date -d '7 day ago' +% ...

  8. LPC43xx SGPIO Pattern Match Mode

    模式匹配 所有位串均具有模式匹配功能. 该功能可用于检测启动代码等.要使用该功能,则必须用需匹配的模式来对REG_SS 编程 (请注意, POS 达到零时 REG_SS 不会与 REG  交换!) M ...

  9. 上海邮政EMS海关清关(个人) 流程

    最近雾埋越来越严重,上个星期买了一个tacx骑行台,不料运气欠佳,被税了.那就去乖乖缴税吧. 拿着EMS的通知单(没有通知单就不要去了),到通知单指定的地址(上海有两处,我的是武定路458号)清关提货 ...

  10. [算法导论]强连通分量 @ Python

    class Graph: def __init__(self): self.V = [] class Vertex: def __init__(self, x): self.key = x self. ...