<C++ primer>里面有这么一段话
拷贝初始化不仅在我们用=定义变量时会发生, 在下列情况下也会发生:
将一个对象作为实参传递给一个非引用的形参
从一个返回类型为非引用类型的函数返回一个对象
从花括号列表....
我为了验证这上面的第二条, 写了如下代码:
#ifndef GEEK_SOLUTION_H
#define GEEK_SOLUTION_H
class A {
public:
A oneMore(){
A b;
return b;
}
void test(A a){
printf("!!!\n");
}
A(A& x){
printf("Copy!\n");
}
A(){
printf("chuangjianle\n");
}
~A(){
printf("xiaohuile\n");
}
A operator=(const A& k){
printf("aaaa\n");
return *this;
}
};
#endif //GEEK_SOLUTION_H
//-----------------------------------------------------
#include <iostream>
#include "Solution.h"
using namespace std;
int main(int argc, char* argv[]) {
A a;
A b = a.oneMore();
printf("1314\n");
return 0;
}
按理来说输出中应该有copy
的, 结果报错, 报错信息如下 :
/Users/zhangzhimin/ClionProjects/geek/main.cpp:9:7: error: no matching constructor for initialization of 'A'
A b = a.oneMore();
^ ~~~~~~~~~~~
/Users/zhangzhimin/ClionProjects/geek/Solution.h:16:5: note: candidate constructor not viable: expects an l-value for 1st argument
A(A& x){
^
/Users/zhangzhimin/ClionProjects/geek/Solution.h:20:5: note: candidate constructor not viable: requires 0 arguments, but 1 was provided
A(){
^
求解答..
新的进展
真是无语了, 按照1L的回复, 我拿vs2015试了下, 居然正常, 真是日了狗了, 不知道这怎么回事...
0x00 总体分析
编译错误因为
没有添加 <cstdio>,就使用 printf()
左值右值,const 概念理解不清,写出 A(A& x) 这样的 copy constructor
是因为你对对于编译器优化了解较少,而且 c++ 标准对此没有明确规定。
0x01 左值、右值、const 与 non-const
首先,你去搜索一什么是左值、右值。这是一个首先出现于编译领域的概念,很容易理解。
然后,我们来看下面这两段你写的代码:
从一个返回类型为非引用类型的函数返回一个对象并用这个对象对另一个同类型对象赋值初始化,确实应该发生两次 copy constructor 的调用,返回对象的时候一次,=初始化的时候一次(不过不一定,见 0x02)。
但是函数
oneMore()
返回类型是一个右值。右值一般被认为是一个不可被用户代码显式改变的值(除了 c++11 中的右值引用和 move 构造函数机制)仅可以传递给一个左值对象或者const 左值引用,这样才可以保证其内容不被改变。一个右值如果传递给一个 non-const 左值引用了,那就意味着用户有可能去改变这个右值的值。所以你的编译器提示你
candidate constructor not viable: expects an l-value for 1st argument A(A& x)
。这里 l-value 就是左值的意思。你应该改写成A(const A& a)
如我没记错的话,C++ Primer 应该也有建议创建 copy constructor 的时候一律使用
A(const A& rhs);
的形式,避免使用A(A& rhs)
。因为前者能涵盖后者。具体的左值右值,const 与 non-const 规则,你可以 Search The Fucking Web.
0x02 编译器优化
A b = a.oneMore();
,这一个表达式要是不优化,要经历如下步骤:进入 oneMore() 函数体,创建要返回的对象 oneMore()::b。(automatic storage, default constructor)
函数返回,将对象 oneMore()::b 复制初始化给 main()::xxx。xxx是一个临时匿名对象,存储
a.oneMore()
的返回值。(automatic storage, copy constructor)A b = xxx
用 xxx 赋值初始化 main()::b (automatic storage, copy constructor)正好,上面这些 C++ primer 也有讲。
我们来算一算,这短短的一个表达式在不优化的情况下我们消耗了多少计算量:三个 constructor,两个 destructor,这要是一个保存数千 string 的 vector。。。
我们先对变量进行拓扑排序:
oneMore()::b -> xxx -> main()::b
可以很明显地看到(即使编译器也能发现):使用 xxx 的时候 oneMore()::b 就没用了,使用 main()::b 的时候 xxx 就没用了。所以编译器会做一些优化来消除一些不必要的中间步骤计算。
优化的过程和算法就不说了,我们来看优化的结果:最后表达式
A b = a.oneMore()
整个计算过程中只有一个变量 main()::b, 所有的操作都作用在了这个变量上。oneMore()::b, xxx 可以说根本没有创建,没创建就意味着不用销毁,这就可以省下两个 constructor 和两个 desctructor。最后这个表达式需要一个 constructor,零个 destructor既然所有操作都作用在同一个对象上了,那么无论在何处打印这个对象(虽然名字不同)的地址也就是一样了。都是
0x7fff52bfd9a8
。