为了验证C++中的复制构造函数和临时对象的问题,我写了一个小程序来研究了一下:
//复制构造函数与临时对象
#include <iostream>
using namespace std;
class A
{
public:
A(){cout<<"A is construting..."<<endl;}
A(const A& a){cout<<"A is cloning..."<<endl;}
~A(){cout<<"A is deconstructing..."<<endl;}
};
A func( A a)
{
return a;
}
int main()
{
A a;
//situation 1
cout<<endl<<"situation 1"<<endl;
cout<<endl<<"用对象a来初始化b,复制构造函数起到了构造函数的作用"<<endl;
A b(a);
//situation 2
cout<<endl<<"situation 2"<<endl;
cout<<endl<<"当传递对象参数和返回对象值时要按值传递,所以是两次复制构造函数"<<endl;
func(a);
//situation 3
cout<<endl<<"situation 3"<<endl;
cout<<endl<<"当传递对象参数和返回对象值时要按值传递,所以是两次复制构造函数,第二次参数传递是用func函数中的临时变量来直接初始化这个新对象c"<<endl;
A c = func(a);
//situation 4
cout<<endl<<"situation 4"<<endl;
cout<<endl<<"当传递对象参数和返回对象值时要按值传递,所以是两次复制构造函数,第二次参数传递是先初始化一个临时对象,再用临时对象初始化新对象d"<<endl;
A d;
d = func(a);
//situation 5
cout<<endl<<"situation 5"<<endl;
cout<<endl<<"新建对象e,用a来初始化e"<<endl;
A e;
e = a;
cout<<endl<<"The main is terminated~~"<<endl;
}
结果如下
得出的结论:
1.可以返回局部变量,但是不能返回局部变量的引用。
2.在return a 时并不是简单的返回这个局部变量,而是返回的是a的一个副本temp,
temp是通过拷贝构造函数实现的 即 A temp(a);也就是说在主函数中用的其实是个temp,而a早在func调用完毕后就释放了,而temp这个对象 暂时在主函数中贮存。
3.temp只是临时构造的,在赋值完毕之后它就析构了,不是一直在主函数中贮存的。
4.在拷贝构造这种情况下,编译器都不再产生副本,而赋值还是不行的。
///@@@@@@@@
///下面的程序说明,代码可以被优化,以来使用或者不使用复制构造函数
#include <string>
#include <iostream>
using namespace std;
class GarCat
{
public:
GarCat(void);
GarCat(int age,int weight);
GarCat(const GarCat & rhs);
GarCat & operator=(const GarCat &hs);
int GetAge(){return itsAge;}
int GetWeight(){return itsWeight;}
void SetAge(int age){ itsAge = age;}
private:
int itsAge;
int itsWeight;
//int itsAge = 5;//error
//只有静态常量数据成员才能在类中初始化
public:
virtual ~GarCat(void);
};
GarCat::GarCat(void)
{
cout<<"GarCat is constructing..."<<endl;
}
GarCat::GarCat(int age,int weight)
{
cout<<"GarCat is constructing..."<<endl;
itsWeight = weight;
itsAge = age;
}
GarCat::GarCat(const GarCat & rhs)
{
cout<<"GarCat copy is constructing..."<<endl;
cout<<this<<" from "<<&rhs<<"copy."<<endl;
itsWeight = rhs.itsWeight;
itsAge = rhs.itsAge;
}
GarCat & GarCat::operator=(const GarCat & rhs)
{
cout<<"GarCat overloaded..."<<endl;
cout<<" from "<<&rhs<<"get value."<<endl;
itsAge = rhs.itsAge;
itsWeight = rhs.itsWeight;
return *this;
}
GarCat::~GarCat(void)
{
cout<<"GarCat is deconstructing..."<<endl;
}
GarCat& func1();
GarCat func2();
GarCat& func3();
void func4(GarCat garcat);
int main()
{
GarCat mycat;
//这个值区别于java,不是赋初值为0,而是随机数
cout<<mycat.GetAge()<<endl;
//错误,这个是私有变量
//cout<<mycat.itsAge<<endl;
cout<<"-----------------------潇洒的分割线----------------------"<<endl;
GarCat &rcat = func1();
int age = rcat.GetAge();
cout<<"rcat is "<<age<<"years old."<<endl;
cout<<"&rcat is "<<&rcat<<endl;
GarCat *pcat = &rcat;
cout<<"pcat is "<<pcat<<endl;
//delete rcat;//不能对引用使用delete
//
///在没有new的时候是不能delete的
///所以下面delete的操作是错误的
/
delete pcat;
//delete好像没有释放内存,怎么获取的还是原来的值
//可能这个内存区域存放的还是原来的?
//先new string 后再调用也没有变,与编译器有关还是什么?
cout<<"没有新建string之前"<<rcat.GetAge()<<endl;
cout<<"没有新建string之前"<<rcat.GetWeight()<<endl;
for(int i=0;i<10;i++)
{
string *s = new string("1234567890");
}
cout<<"新建string之后"<<rcat.GetAge()<<endl;
cout<<"新建string之后"<<rcat.GetWeight()<<endl;
//这是问题来了,rcat.getAge()为123了
GarCat *pcat2 = new GarCat(123,444);
cout<<"pcat2 is "<<pcat<<endl;
cout<<"delete pcat后使用rcat会发生什么问题??"<<endl;
cout<<"delete pCat后 &rCat = "<<&rcat<<endl;
cout<<"delete pCat后 rCat.age = "<<rcat.GetAge()<<endl;
cout<<"delete pCat后 rCat.weight = "<<rcat.GetWeight()<<endl;
cout<<"-----------------------潇洒的分割线----------------------"<<endl;
GarCat mycat2 = func1();
cout<<"myCat2的地址是 "<<&mycat2<<endl;
cout<<endl<<"-------------------------------------------------------"<<endl;
GarCat mycat5;
func4(mycat5);
return 0;
}
GarCat & func1()
{
GarCat *pgarfield = new GarCat(5,9);
cout<< "pgarfield: "<< pgarfield <<endl;
return *pgarfield;
}
GarCat func2()
{
GarCat tmp;
cout<<"int the func2 tmp pointer "<<&tmp<<endl;
tmp.SetAge(9999);
return tmp;
}
/*
GarCat &func3()
{
GarCat tmp;
cout<<"int the func3 tmp pointer "<<&tmp<<endl;
tmp.SetAge(9999);
return tmp;
}
*/
void func4(GarCat garcat)
{
cout<<"形式参数garcat的地址"<<&garcat<<endl;
}
先说说第一块吧,
这个貌似应该调用拷贝构造的地方没有调用拷贝构造,应该是编译器做的优化,可以参考《深入探索C++对象模型》P66页。
按照书中的说法,很可能只创建了1个对象
X bar()
{
X xx;
return xx;
}
可能会被编译器优化成
void bar(X & _result)
{
_result.X::X();
return;
}
所以调用X a = bar(),其实被转换成 bar(X& a);
所以一个本该调用拷贝构造的地方却很可能调仅用了构造函数来完成工作。
这个问题我也没有搞明白,我没有试过lz的代码是不是会这样,如果真这样的话,我觉得编译器就管的太多了,如果拷贝构造中有一些特殊的功能呢(就像楼主有个输出语句),岂不是无声无息中被抹杀了。这个功能被称为NRV,好像一直没有人对这个问题给出非常明确的回答
------------------------------------------------------------------------------------------------------------------------------
thx,在vs2005中代码优化开启时,第一块确实只会调一个构造函数
把代码优化禁用后,就会调用拷贝构造函数了
确实是个NRV的问题
http://blog.vckbase.com/bruceteen/archive/2005/12/30/16652.html
------------------------------------------------------------------------------------------------------------------------------
正如4楼的所说~~~
到底创建多少临时对象,要视编译器而定,看编译器是否采用NVR...
(其实一般情况下,NVR都是未采用的~),因此对后三个注释代码做
如下分析:写出编译后的伪代码(仅供参考)
编译器更改后的函数原型void TheFunctionTwo(SimpleCat&)
注释1:
SimpleCat myCat3;
TheFunctionTwo(myCat3)
{
SimpleCat tempCat;
templCat.SimpleCat::SimpleCat();
cout<<"in TheFunctionTwo tempCat指针 "<<&tempCat<<endl;
tempCat.SetAge(9999);
myCat3.SimpleCat::SimpleCat(tempCat);
tempCat.SimpleCat::~SimpleCat();
}
注释2:
SimpleCat myCat4;
myCat4.SimpleCat::Simple();
SimpleCat temp;
TheFunctionTwo(temp)
{
SimpleCat tempCat;
templCat.SimpleCat::SimpleCat();
cout<<"in TheFunctionTwo tempCat指针 "<<&tempCat<<endl;
tempCat.SetAge(9999);
temp.SimpleCat::SimpleCat(tempCat);
tempCat.SimpleCat::~SimpleCat();
}
myCat4.operator=(temp);
temp.SimpleCat::~SimpleCat();
注释3:
//SimpleCat &rmyCat = TheFunctionTwo();
SimpleCat temp;
SimpleCat &rmyCat=temp;
TheFunctionTwo(temp)
{
SimpleCat tempCat;
templCat.SimpleCat::SimpleCat();
cout<<"in TheFunctionTwo tempCat指针 "<<&tempCat<<endl;
tempCat.SetAge(9999);
temp.SimpleCat::SimpleCat(tempCat);
tempCat.SimpleCat::~SimpleCat();
}
这样就知道到底需要多少临时对象了~~~~