引用与拷贝构造函数,----不经意之间发现了一个很奇怪的现象!!!
class HowMany2
{
string name;
public: static int objectCount;
public:
HowMany2(const string & id =""):name(id)
{
++objectCount;
print("howmant2()");
}
~HowMany2(void)
{
--objectCount;
print("xigou --howmany2()");
}
HowMany2(const HowMany2 & h):name(h.name )
{
name+= "copy";
++objectCount;
print("HowMany2(const howmany2 &)--");
}
HowMany2 & operator =(const HowMany2 & h)
{
cout<<"operater =*****************************"<<endl;
name=h.name ;
return *this;
}
void print(const string & msg)const
{
if(msg.size ()!=0)
std::cout<<msg<<endl;
std::cout<<'\t'<<name<<":"<<"objectCount="<<objectCount<<endl;
}
}
HowMany2 x("x");
HowMany2 & getX()
{
return x;
}
void main()
{
HowMany2 h=getX();
}
程序的结果对于大家来说,肯定是小菜一碟,但是,对getx()作如下修改,结果
竟然不变!!!
HowMany2 getX()
{
return x;
}
是不是很奇怪啊?????????
函数的返回值,定义成"引用"和"非引用",竟然答案一样??????
问题点数:100、回复次数:19Top
1 楼reinhard_liu(reinhard)回复于 2003-09-04 17:06:51 得分 50
因为你定义的copy constructor 可能启动了系统的NRV(named return value)优化
所以
HowMany2 getX()
{
return x;
}
会变成如下的c 伪吗
HowMany2 getX(Howmany2 & _result)
{
return _result.HowMany2(x);(返回值得中间临时变量没有了,优化掉了)
}
Top
2 楼reinhard_liu(reinhard)回复于 2003-09-04 17:12:17 得分 0
p.s. sorry 优化后的代码我笔误现在修正如下
返回value 的优化
//c 伪代码
void getX(Howmany2& _result)
{
return _result.HowMany2(x);(返回值得中间临时变量没有了,优化掉了)
}
返回value 的无优化
void getX(Howmany2& _result)
{
Howmany2 temp;
Howmany2.Howmany2(x);
return _result.operator=(x);(返回值得中间临时变量有了)
}
Top
3 楼reinhard_liu(reinhard)回复于 2003-09-04 17:14:18 得分 0
p.s. 又打错了 sorry :)
第2个伪代码
Howmany2.Howmany2(x); //改为 temp.Howmany2(x);Top
4 楼xueweizhong(薛卫忠)回复于 2003-09-04 17:27:51 得分 30
返回引用只有在以下情况才和返回值有差别:
1)返回值被引用
T& t = f(...);
这种情况叫做 reference's direct-binding
会出现在以下情况里:
1>ref-para binding
void g(T&);
g(f(...));
2>ctor binding
struct Foo
{
Foo(T&);
...
};
Foo foo(f(...));
3)其他一些情况。
2) 返回值被取地址
T* pt = &f(...);
其他情况下返回的引用当值处理,没有任何优越性,
倒是会导致一些常见错误,
如返回函数调用栈上对象的引用(bad-ref)。
Top
5 楼fishsward(.)回复于 2003-09-04 17:30:00 得分 0
我是楼主,现在说一下我的理解:
to : reinhard_liu(reinhard
您的意思是 返回value时,把临时变量优化掉了,我不认为这样
结合输出结果,我的理解:,
1.返回value时
编译器还是构造了"x的拷贝",此拷贝就是您说的临时变量,它被创建
在h中
2.返回引用时
编译器在函数返回的那一刹那间,没有创建临时变量,它返回的是全局
对象x的引用,然后用"x的引用"构造h
--------也就是说,两者都调用了拷贝构造函数
如果改成如下:
HowMany2 x("x");
HowMany2 & getX()
{
return x;
}
void main()
{
getX();
}
和---------------------------------------------
HowMany2 x("x");
HowMany2 getX()
{
return x;
}
void main()
{
getX();
}
答案就不同,前者一共只有一次构造函数的调用,而后者却有两次构造函数的调用
Top
6 楼reinhard_liu(reinhard)回复于 2003-09-04 17:34:20 得分 0
经过NRV 优化少了一次constructor的调用所以结果会和你的猜想有区别Top
7 楼eric8231(1328cire)回复于 2003-09-04 17:35:22 得分 10
由于程序是 HowMany2 h=getX(); 所以在调用getX(); 时,不论getX();返回的是引用还是值,getX();的结果都将作为 对象h 的拷贝构造函数的参数。
由于拷贝构造函数的参数定义为“const HowMany2 &”,所以在两种情况下编译器都会将那个静态对象x的引用传到函数中。
Top
8 楼fishsward(.)回复于 2003-09-04 17:44:06 得分 0
我是楼主,同意xueweizhong(薛卫忠) 的观点,
不同意reinhard_liu(reinhard)的观点,因为返回value时
"临时变量"并没有被优化掉
我的理解:
1
HowMany2 x("x");
HowMany2 & getX()
{
return x;
}
void main()
{
HowMany2 h=getX();
}
此时,getx()返回的是全局对象x的引用,然后用此引用,通过拷贝构造函数对
h进行初始化.
--------------------------------
2
HowMany2 x("x");
HowMany2 getX()
{
return x;
}
void main()
{
HowMany2 h=getX();
}
此时在getx()返回前,构造了x的拷贝,编译器直接将此"x的拷贝"(也就是您说的临时对象)创建
在h中
以上两种情况都调用两次构造函数,结果相同!!!
但是如下情况,就不相同了:
1.
HowMany2 x("x");
HowMany2 & getX()
{
return x;
}
void main()
{
getX();
}
-------------------------------------
2.
HowMany2 x("x");
HowMany2 getX()
{
return x;
}
void main()
{
getX();
}
前者调用两次构造函数,后者只调用一次构造函数
Top
9 楼reinhard_liu(reinhard)回复于 2003-09-04 17:55:58 得分 0
-------------------------------------------
但是如下情况,就不相同了:
1.
HowMany2 x("x");
HowMany2 & getX()
{
return x;
}
void main()
{
getX();
}
-------------------------------------
2.
HowMany2 x("x");
HowMany2 getX()
{
return x;
}
void main()
{
getX();
}
前者调用两次构造函数,后者只调用一次构造函数
返回reference 调用两次constructor?
Top
10 楼reinhard_liu(reinhard)回复于 2003-09-04 17:59:39 得分 0
从语义上说 返回 reference 肯定要比返回value多一次constructor,但是实际上因为优化和具体使用的限制可能两者接近啊Top
11 楼fishsward(.)回复于 2003-09-04 18:04:12 得分 0
to reinhard_liu(reinhard:
不好意思,刚好写反了,前者调用1次构造函数,后者只调用2 次构造函数
并且跟优化好像关系不大!
Top
12 楼reinhard_liu(reinhard)回复于 2003-09-04 18:16:57 得分 0
如果你的编译器支持NRV的情况那么在返回值被接受的情况下
Howmany2 h=getX();
优化和不优化肯定会有一次constructor的调用差异,你仔细考虑一下我的话
NRV优化与否也看具体的编译器和实际的上下文,并且只有有COPY CONSTRUCTOR的CLASS才优化
我现在也看的有点混乱了
你把你的观点提清楚一点,好么?Top
13 楼fishsward(.)回复于 2003-09-04 18:33:05 得分 0
to reinhard_liu(reinhard) :
其实关于此问题,我有自己的理解,我把问题提出来,是因为我不敢确认!
对于优化,我觉得编译器不大可能把返回value的"临时对象"优化掉!
1
HowMany2 x("x");
HowMany2 & getX()
{
return x;
}
void main()
{
getX();
}
getx()返回的是"引用",所以没有"临时对象",所以只有"HowMany2 x("x")"的一次构造函数的调用
-------------------------------------
2.
HowMany2 x("x");
HowMany2 getX()
{
return x;
}
void main()
{
getX();
}
getx返回的是value,调用它后,在即将返回的那一刹那,构造x的拷贝,由于生存期已到,
这个"临时对象"(x的拷贝)马上释放.所以一共调用两次构造函数
Top
14 楼reinhard_liu(reinhard)回复于 2003-09-04 20:21:41 得分 0
关于临时对象优化的NRV 不是我发明出来的,所以我想你可以参考一下<<深度探索c++对象模型>>,至于你个问题到底有没有优化,我不敢说,不过我觉得你的程序应该参考一下这个说法,从语义上讲你的说法是对的,但是c++的优化是为了效率的考虑,当然也有过NRV之后效率反而下降的例子,更甚至有过出错的目标代码。对于NRV的争论很多,我也不甚了解。
我认为对于c++的理解我还处于初级阶段,也希望有高手来给这个问题一个基本圆满的解答。
:)Top
15 楼lth_81(上海男生)回复于 2003-09-04 20:22:51 得分 10
环境vc.net
结论1:
2种情况下都只用了一次copy constructor,因此都用了nrv优化。
结论2:
返回引用或者说返回的是值在这里都不会构成任何的影响,因为变量h不是引用或者指针。
结论3:
如果是getx()这样单独调用的话,不会调用copy constructor,可能原因是没有必要返回,在外层,更本没有对getx()的返回值操作。
另外一个问题是(注意在这里getx()返回的是一个局部变量):
(1)引用返回的getx()可以这样用getx().member = 123;因为它发挥的根本就是局部变量的一个引用。
(2)返回值的getx()如果调用getx().member = 123;是会报错的。因为其中用到了一个隐含的由编译器产生的常量,因为是常量所以不能对其进行修改。
可见,返回引用和返回值是有区别的。问题在于外层接受的变量类型。如果外层接受的变量是引用类型,那么其实不会调用copy constructor也不会有什么nrv的操作;相反如果外层接受的变量不是引用类型的,那么会调用copy constructor。
用到的例子代码:
#include <iostream>
#include <conio.h>
using namespace std;
class test{
public:
int y;
test() { cout << "default constructor" << endl; }
test(test& tst) { cout << "copy constructor" << endl; }
test& operator=(const test& i) { cout << "operator" << endl;}
};
test& getx()
{
test x;//这里返回的是局部变量,更能说明问题
x.y = 123;
return x;
}
int main()
{
test& y = getx();
system("pause");
}Top
16 楼xueweizhong(薛卫忠)回复于 2003-09-04 21:21:48 得分 0
看了你们在这边的讨论,又去翻看了C++STD98/chapter 8
得出以下结论:
1)
在没有NRV的情况下:
调用 T t = getX();
如果 getX()返回引用,将只有一个copy-ctor
如果 getX()返回值 ,将有两个copy-ctor
(if NRV, only 1 copy-ctor, but it's not STD WAY)
---------(#)
2)
如果copy-ctor含有side-effect,
那么两次调用和一次调用的导致的程序运行结果肯定是不同的。
3)
从2)的意义上说,返回引用和返回值还是有些不同的。
但同时由(#),也说明是否使用NRV是会影响程序结果的。
4)
如果允许使用NRV,认为程序执行结果不会变,
那么在这个意义上我们也认为
返回引用和返回值的差别(在这个用法中)也不会影响程序执行结果。
5)
所以C++98不允许使用NRV。
(对之避而不谈)
其他:(^-^)
我不是很明白 lth_81(上海男生) 在说什么?再次请教。
Top
17 楼fishsward(.)回复于 2003-09-05 13:31:12 得分 0
继续聆听大家的高见Top
18 楼eric8231(1328cire)回复于 2003-09-05 18:16:36 得分 0
>>函数的返回值,定义成"引用"和"非引用",竟然答案一样
结果一样的原因并不是由于返回“引用”和返回“值”的区别,而是由于函数getX()的调用形式:
1. 如果是 HowMany2 h=getX();
那么不论是返回“引用”还是“值”都没有必要产生临时变量,因为可以直接将那个静态变量作为拷贝构造函数的参数。这与NRV优化似乎没有关系。
2. 如果是 getX();
我在Dev-c++编译的结果是:当返回“引用”时没有临时变量;返回“值”时产生临时变量。
我想,编译器这样做是由于当返回“值”时,虽然这个值以后不会用到,但仍然产生出一个临时变量来“承接”这个值。
但我相信在第二种调用情况下产生的任何临时变量都不是必要的,担由于编译器需要有一些一致性的规则,所以这个临时变量仍然被构造出来。
Top
19 楼yndfcd(YNDFCD)回复于 2003-09-05 21:09:37 得分 0
为什么在VC6。0下得到的程序调用了两次constructor,只是调用了一次destructor呢?Top
20 楼fishsward(.)回复于 2003-09-05 22:51:07 得分 0
upTop



