C++ 中函数值返回的过程中的问题,是否创建临时变量

红薯 发布于 2011/03/24 19:22
阅读 1K+
收藏 2

Sales_item operator+(const Sales_item& lhs,const Sales_item& rhs)
{
Sales_item ret(lhs) ;
ret+= rhs ;
return ret ;
}
怎么可以返回一个局部变量呢??????
例如
Sales_item item1,item2,item3 ;
......(item2,item3初始化成员变量)
item1 = item2 + item3 ;

没有问题,它返回的是一个值,而不是引用,所以是正确的。在item1 = item2 + item3 中发生了一次值拷贝(赋值),也就是将item2+item3返回的局部变量拷贝给了item1之后,局部变量的作用域结束

可以返回局部变量,但是不能返回局部变量的引用。理解区分值和引用这两个概念是学习C++的一大关键,明白这两个概念之后,你就会理解为什么C++的类里面需要有拷贝构造函数,赋值操作符,析构函数三个元素了以及其它的一些稀里古怪的用法和忠告了
--

Sales_item operator+(const Sales_item& lhs,const Sales_item& rhs)
{
Sales_item ret(lhs) ;
ret+= rhs ;
return ret ;
}
在return ret 时并不是简单的返回这个局部变量,而是返回的是ret的一个副本temp,
temp是通过拷贝构造函数实现的 即 Sales_item temp(ret);也就是说在主函数中用的其实是个temp,而ret早在operator+调用完毕后就释放了,而temp这个 对象一直在主函数中贮存(虽然显示是看不到的 ) ????

嗯嗯,临时对象一直是个有争议的话题。temp只是临时构造的,在赋值完毕之后它就析构了,不是一直在主函数中贮存的。这个临时对象的作用域是什么???
在你提的这种情况下,其实编译器是可以优化掉这个副本的,但是可惜C++标准只允许优化 “Sales_item item1=item2+item3” 这种情况的,也就是在拷贝构造这种情况下,编译器都不再产生副本,而赋值还是不行的。也许在未来的C++标准由可能通过拓展语义来消除临时对象,毕竟临时 对象成为影响C++效率的一个主要因素。
--

???
目前的C++标准不允许在赋值的时候优化掉函数返回的副本,也就是下面这两种情况是不一样的

//这是赋值给item1,目前的标准下是会产生副本 temp
Sales_item item1,item2,item3;
item1=item2+item3;

//这是拷贝构造item1,编译器通常会优化(这个优化区别下面所说的NRV优化)掉副本,也就是不产生副本 temp
Sales_item item2,item3;
Sales_item item1=item2+item3;

看一下代码中的TheFunctionTwo();

1 #include
2 #include
3
4 using namespace std;
5
6 class SimpleCat
7 {
8 public:
9 SimpleCat(void);
10 SimpleCat(int age,int weight);
11 SimpleCat(SimpleCat &rsc);
12 SimpleCat & operator=(const SimpleCat &rhs);
13 int GetAge(){return itsAge;}
14 int GetWeight(){return itsWeight;}
15 void SetAge(int age){itsAge = age;}
16
17 private:
18 int itsAge;
19 int itsWeight;
20 //int itsAge=5;//1>.\SimpleCat.cpp(14) : error C2864: “SimpleCat::itsAge”: 只有静态常量整型数据成员才可以在类中初始化
21
22 public:
23 virtual ~SimpleCat(void);
24 };
25
26 SimpleCat::SimpleCat(void)
27 {
28 cout<<"SimpleCat constructor "<<this<<endl;
29 }
30
31 SimpleCat::SimpleCat(int age,int weight)
32 {
33 cout<<"SimpleCat constructor "<<this<<endl;
34 itsAge = age;
35 itsWeight = weight;
36 }
37
38 SimpleCat::SimpleCat(SimpleCat &rhs)
39 {
40 cout<<"SimpleCat copy constructor"<<this<<" 从 "<<&rhs<<"拷贝"<<endl;
41 itsAge = rhs.itsAge;
42 itsWeight = rhs.itsWeight;
43 }
44
45 SimpleCat &SimpleCat::operator=(const SimpleCat &rhs)
46 {
47 cout<<"SimpleCat重载赋值运算符"<<this<<" 从 "<<&rhs<<"赋值"<<endl;
48 itsAge = rhs.itsAge;
49 itsWeight = rhs.itsWeight;
50 return *this;
51 }
52
53 SimpleCat::~SimpleCat(void)
54 {
55 cout<<"SimpleCat destructor"<<this<<endl;
56 }
57
58 SimpleCat &TheFunction();
59
60 SimpleCat TheFunctionTwo();
61
62 SimpleCat &TheFunctionThree();
63
64 void TheFunctionFour(SimpleCat simpleCat);
65
66 int main()
67 {
68 SimpleCat myCat;
69 cout<<myCat.GetAge()<<endl;//这个值区别于java不是赋初值为0的,而是一个随机的值
70 // cout<<myCat.itsAge<<endl;
71
72 cout<<"------------------------"<<endl;
73 SimpleCat &rCat = TheFunction();
74 int age = rCat.GetAge();
75 cout<<"rCat is "<<age<<"yeas old!"<<endl;
76 cout<<"&rCat: "<<&rCat<<endl;
77 SimpleCat *pCat = &rCat;
78 //delete rCat;//不能对引用使用delete
79 delete pCat;
80 //delete好像没有释放内存,怎么获取的还是原来的值
81 //可能在这个内存区域存放的还是原来的?先new string后再调用也没变,与编译器有关还是什么??
82 for(int i =0;i<10;i++)
83 {
84 //想通过创建string对象来填充之前的内存区间,好像没用
85 string *s = new string("abcdefghijklmn");
86 }
87
88 //这时问题来了,rCat.getAge()为123了
89 SimpleCat *pSecond = new SimpleCat(123,444);
90
91 cout<<"delete pCat后再使用rCat引用会发生什么问题???"<<endl;
92 cout<<"delete pCat后 &rCat"<<&rCat<<endl;
93 cout<<"delete pCat后 rCat.age"<<rCat.GetAge()<<endl;
94
95 cout<<"--------------------------"<<endl;
96 SimpleCat myCat2 = TheFunction();//这个会发生内存泄漏,在函数中申请的内存会得不到释放
97 //myCat2是通过默认的拷贝函数来进行初始化的
98 cout<<"myCat2的地址是 "<<&myCat2<<endl;
99 cout<<endl<<"---------------------------"<<endl;
100
101 //直接的调用默认拷贝构造函数的
102 //cout<<"直接的调用默认拷贝构造函数的,这个语句一共创建了多少个对象"<<endl;
103 //SimpleCat myCat3 = TheFunctionTwo();//这个尽然没调用拷贝构造函数?????
104 //cout<<"myCat3的地址是 "<<&myCat3<<endl;
105 //cout<<"myCat3.GetAge() "<<myCat3.GetAge()<<endl;
106
107 //调用默认的赋值运算符的
108 //cout<<"调用默认的赋值运算符的,这个语句一共创建了多少个对象"<<endl;
109 //SimpleCat myCat4;
110 //myCat4 = TheFunctionTwo();
111 //cout<<"myCat4.GetAge() "<<myCat4.GetAge()<<endl;
112
113
114 //这种调用的方式
115 //cout<<"TheFunctionTwo()返回的临时对象赋给一个引用"<<endl;
116 //SimpleCat &rmyCat = TheFunctionTwo();
117 //cout<<"打印一下返回的临时对象的地址,临时对象赋给引用之后就不会立即析构了---"<<&rmyCat<<endl;
118 //cout<<"rmCat.GetAge() "<<rmyCat.GetAge()<<endl;
119
120 SimpleCat myCat5;
121 TheFunctionFour(myCat5);
122
123 return 0;
124 }
125
126
127 //这个函数在返回时,是否会创建一个临时引用变量???
128 //TheFunctionTwo(SimpleCat &simpleCat)这个函数在传递参数时是否会创建一个临时的引用变量
129 //临时的引用变量的作用域范围是什么
130 SimpleCat &TheFunction()
131 {
132 SimpleCat *pFrisky = new SimpleCat(5,9);
133 cout<<"pFrisky: "<<pFrisky<<endl;
134 return *pFrisky;
135 }
136
137
138
139 //我要看对象被创建了几次,是否创建临时对象
140 SimpleCat TheFunctionTwo()
141 {
142 SimpleCat tempCat;
143 cout<<"in TheFunctionTwo tempCat指针 "<<&tempCat<<endl;
144 tempCat.SetAge(9999);
145 //返回时是否还创建一个临时对象
146 return tempCat;
147
148 }
149
150
151 //这种方式肯定是错的,返回了局部变量的引用
152 SimpleCat &TheFunctionThree()
153 {
154 SimpleCat tempCat;//局部变量
155 cout<<"in TheFunctionThree tempCat指针 "<<&tempCat<<endl;
156 tempCat.SetAge(9999);
157 //返回时是否还创建一个临时对象,已引用的形式
158 return tempCat;
159 }
160
161
162
163 //我要看再实参对形参进行拷贝构造时,是否会打印出调用的拷贝函数信息
164 void TheFunctionFour(SimpleCat simpleCat)
165 {
166 cout<<"形式参数simpleCat的地址 "<<&simpleCat<<endl;
167
168 }
169
170
171

------------------------------------------------------------------------------------------------------------------------------
先说说第一块吧,
这个貌似应该调用拷贝构造的地方没有调用拷贝构造,应该是编译器做的优化,可以参考《深入探索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();
}
这样就知道到底需要多少临时对象了~~~~

加载中
返回顶部
顶部