印象中,函数调用的时候,参数past by value、返回值return by value,总是伴随着对象的复制。
实际上参数传递是这样的,但是返回值有可能不是这样的(虽然大部分都是面临拷贝构造函数的调用),这取决于编译器。
#include<string>
#include<list>
#include<iostream>
using namespace std;
class C
{
public:
C()
{
cout<<"C default constructor(),this="<<this<<endl;
}
C(const C &c)
{
cout<<"C const copy constructor(),this="<<this<<",reference="<<&c<<endl;
}
C(C &c)
{
cout<<"C nonconst copy constructor(),this="<<this<<",reference="<<&c<<endl;
}
const C & operator=(const C &c)
{
cout<<"C assignment(),this="<<this<<endl;
return *this;
}
~C()
{
cout<<"C destructor(),this="<<this<<endl;
}
};
C test_1(int i)
{
cout<<"entering test_1"<<endl;
C x;
C a; //a会析构
cout<<"leaving test_1"<<endl;
return x; //return之后栈不清空,x不会析构,即使编译器已经将优化设置成-O0
}
C test_2(int i)
{
cout<<"entering test_2"<<endl;
C x;
C a;
cout<<"leaving test_2"<<endl;
if(i>0)
return x;
else
return a; //x和a都会析构,返回的时候,先调用拷贝构造函数,初始化返回值(此处为main里面的z),
//然后再析构a和x
}
C test_3(C t)
{
return t; //此处导致t的构造和析构
}
C test_4(C t)
{
C x=t;
return x; //此处导致t的构造和析构,但是x只会构造不会析构
}
int main()
{
cout<<"invoking test_1"<<endl;
C y=test_1(1); //这种调用不会有拷贝构造函数,y直接为test_1函数栈里面生成的对象,编译器优化的结果
cout<<"end invoke test_1"<<endl;
cout<<"================华丽分割线================="<<endl;
cout<<"invoking test_2"<<endl;
C z=test_2(1); //这种情况会调用拷贝构造函数(nonconst版本),初始化z
cout<<"end invoke test_2"<<endl;
cout<<"================华丽分割线================="<<endl;
cout<<"invoking test_3"<<endl;
C a=test_3(y);
cout<<"end invoke test_3"<<endl;
cout<<"================华丽分割线================="<<endl;
cout<<"invoking test_4"<<endl;
C b=test_4(y);
cout<<"end invoke test_4"<<endl;
cout<<"================华丽分割线================="<<endl;
cout<<"开始测试临时变量何时析构"<<endl;
test_2(1), //(注意结束处是逗号)此处返回的C没有指定任何变量,编译器会生成临时变量
cout<<"结束测试临时变量何时析构"<<endl;//临时变量会再语句的第一个分号处析构,cout完成之后析构
cout<<"================华丽分割线================="<<endl;
cout<<"开始测试临时变量何时析构"<<endl;
test_2(1); //(注意结束处是分号)此处返回的C没有指定任何变量,编译器会生成临时变量
cout<<"结束测试临时变量何时析构"<<endl;//临时变量会再语句的第一个分号处析构,cout开始之前析构
cout<<"================华丽分割线================="<<endl;
cout<<"================下面开始析构栈里面的变量了,啦啦啦================="<<endl;
cout<<"================析构顺序按照入栈的顺序,后进先出,后构造,先析构==========="<<endl;
return 0;
}
运行结果:
AlexdeMacBook-Pro:~ alex$ a.out
invoking test_1
entering test_1
C default constructor(),this=0x7fff5929baa8
C default constructor(),this=0x7fff5929b8d8
leaving test_1
C destructor(),this=0x7fff5929b8d8
end invoke test_1
================华丽分割线=================
invoking test_2
entering test_2
C default constructor(),this=0x7fff5929b8d8
C default constructor(),this=0x7fff5929b8d0
leaving test_2
C nonconst copy constructor(),this=0x7fff5929ba98,reference=0x7fff5929b8d8
C destructor(),this=0x7fff5929b8d0
C destructor(),this=0x7fff5929b8d8
end invoke test_2
================华丽分割线=================
invoking test_3
C nonconst copy constructor(),this=0x7fff5929ba88,reference=0x7fff5929baa8
C nonconst copy constructor(),this=0x7fff5929ba90,reference=0x7fff5929ba88
C destructor(),this=0x7fff5929ba88
end invoke test_3
================华丽分割线=================
invoking test_4
C nonconst copy constructor(),this=0x7fff5929ba78,reference=0x7fff5929baa8
C nonconst copy constructor(),this=0x7fff5929ba80,reference=0x7fff5929ba78
C destructor(),this=0x7fff5929ba78
end invoke test_4
================华丽分割线=================
开始测试临时变量何时析构
entering test_2
C default constructor(),this=0x7fff5929b8d8
C default constructor(),this=0x7fff5929b8d0
leaving test_2
C nonconst copy constructor(),this=0x7fff5929ba70,reference=0x7fff5929b8d8
C destructor(),this=0x7fff5929b8d0
C destructor(),this=0x7fff5929b8d8
结束测试临时变量何时析构
C destructor(),this=0x7fff5929ba70
================华丽分割线=================
开始测试临时变量何时析构
entering test_2
C default constructor(),this=0x7fff5929b8d8
C default constructor(),this=0x7fff5929b8d0
leaving test_2
C nonconst copy constructor(),this=0x7fff5929ba68,reference=0x7fff5929b8d8
C destructor(),this=0x7fff5929b8d0
C destructor(),this=0x7fff5929b8d8
C destructor(),this=0x7fff5929ba68
结束测试临时变量何时析构
================华丽分割线=================
================下面开始析构栈里面的变量了,啦啦啦=================
================析构顺序按照入栈的顺序,后进先出,后构造,先析构===========
C destructor(),this=0x7fff5929ba80
C destructor(),this=0x7fff5929ba90
C destructor(),this=0x7fff5929ba98
C destructor(),this=0x7fff5929baa8
AlexdeMacBook-Pro:~ alex$
AlexdeMacBook-Pro:~ alex$
结论:
一:return by value时候编译器的优化
编译器在能够做优化的时候,会尽量帮你做优化,比如test_1,总是将栈里面的x直接给调用者,避免了多一次的析构和构造。即使在关闭编译器优化的时候,它依然给你做了这个动作。但是在test_2里面,返回值是动态的,随着参数变动而变动,编译器是没有办法得知保留哪个的,于是索性都析构了。
在Effective C++ Item 21,page 94, Don't try to return a reference when you must return an object.
作者说:C++和所有编程语言一样,允许编译器实现者施行最优化,用以改善产出码的效率却不改变其可观察的行为。
g++确实对此做了优化,但是动态返回值,编译器却无能为力。你无法要求编译器按照每个分支,生成不同的代码。否则在复杂的程序下,生成的可执行文件大小那将无法估计了
二:临时变了的析构时机
临时变量,总是在执行完生成临时变量的那一行代码之后析构
(是不是比较拗口?)
那就这样说吧:生成临时变量之后,遇到第一个分号,析构函数开始调用