大家都知道一个常识:“千万不要返回局部对象或变量的引用和指针”。
我一直很纳闷,既然所有C++权威的书上都要求“一定不要返回局部对象或变量的引用和指针”,那为什么C++编译器不从语法上直接禁掉这种用法,让你编译通不过(在技术上应该不难实现的)。如果只是建议的话,那么“返回局部对象或变量的引用和指针”是否有用武之地呢?(从理论上来讲,我认为这种做法似乎总是错误的,原因大家都知道。)
EX(1)
#include
using namespace std;
class CComplex
{
public:
CComplex():real(0),image(0){}
CComplex(double real,double image):real(real),image(image){}
CComplex& operator+(const CComplex& second)
{
CComplex temp(real+second.real,image+second.image);
return temp;
}
void Print()
{
cout《"("《real《"+"《image《"i)"《endl;
}
private:
double real;
double image;
};
int main()
{
CComplex a(2,4);
CComplex b(1.5,3.5);
CComplex c=a+b;
c.Print();
return 0;
}
operator+返回的是临时对象的引用,为什么能正确地工作???
EX(2)
#include
#include
using namespace std;
string& f()
{
string s("hello");
return s;
}
int main()
{
cout《f()《endl;
return 0;
}
同样是对象,为什么string对象就不行,就因为string比较特殊???
EX(3)
#include
#include
using namespace std;
double& f()
{
double d(5.55);
return d;
}
int main()
{
cout《f()《endl;
return 0;
}
为什么内置类型(int,float等均可)返回局部变量的引用总可以正确地工作???
这个问题似乎以前已经有人讨论过,但一直没有定论。
不要跟我说运行正确是因为我运气好,运气不好地话就输出任意值;
我运行了N次,未见任何异常,也不要说运行上千万次才有可能出问题;
我在GCC和VS2010上都验证过了,我觉得是不是编译器做了相应的优化啊(特别是针对内置基本类型)。
有想法的兄弟望赐教,感激不尽!!
int main()
{
CComplex a(2,4);
CComplex b(1.5,3.5);
CComplex c=a+b;
c.Print();
return 0;
}
operator+返回的是临时对象的引用,为什么能正确地工作???
答:main函数在执行之后,a,b入栈,接着a+b调用了operator+,temp也入栈,operator+执行完后,temp出栈并调用析构函数,由于出栈仅仅是移动了PC指针,而你又未写析构函数将CComplex清零,因此temp所占的那块栈空间的内存依然保持原样,只是PC指针已经不再指向它,而operator+返回的引用其实指向的是temp所占内存,然后在调用CComplex的默认拷贝构造的函数的时候,由于拷贝构造函数的输入参数也是引用,因此也指向temp那块内存,对此快内存也会按照CComplex类型来进行访问,最后c就得到了temp的内容。这里即使是写成CComplex& c=a+b;结果也是能输出temp的内容的。此时你若在此句话后面再加几个函数调用,这些函数必须要有参数或内部定义有变量,然后再c.Print(),你会发现结果完全变了。
EX(2)
#include
#include
using namespace std;
string& f()
{
string s("hello");
return s;
}
int main()
{
cout《f()《endl;
return 0;
}
同样是对象,为什么string对象就不行,就因为string比较特殊???
答:因为s在出栈的时候其析构函数会将内存都清掉,在外面还想访问自然访问不成功了。
EX(3)
#include
#include
using namespace std;
double& f()
{
double d(5.55);
return d;
}
int main()
{
cout《f()《endl;
return 0;
}
答:理解了上面两个答案,这个我就不用多说了吧。
每个人必有其背后的深刻原因,只是受限于种种因素,人们不可能都去搞明白。更多时候,并不是原因不充分,只是人们以其自己的知识背景还不足以理解。
一、为什么不禁用的问题
为什么不禁引用返回局部变量,技术上真的是不难吗?且,有足够的必要吗?请见以下例子:
int *f1(int &ri)
{
return &ri;
}
int *f2
{
int i=4;
int *j;
j=f1(i);
return j;
}
int main()
{
int *p=f2;
*p=6;
return 0;
}
p在初始化后,*p生命期是否已经结束了呢?我相信,如果这件事也得由编译器去判断,那么显然,程序员全部可以下岗了,编译器实在是太智能了,人还有必要存在吗?但现有技术真的能吗?如果能的话,要花多大开销,这个开销有必要吗?“千万不要返回局部对象或变量的引用和指针”应该是个原则性的东西,它是个典型代表,其实大原则是“不要在自动变量(不管是表达式中间结果的临时变量(如果它不能保证总优化到寄存器中)还是源程序中有明确名字的auto变量)生命期结束后还试图解引用它”。
程序设计语言课一般会说语言的可写性与可读性是对矛盾,C语言的可写性特别强,既会给比较强的人非常灵活的选择,又会让入门者走不少弯路或者半途而废。利器不是谁都能用得好,这与水平不水平没什么关系,说人的水平不足够使用C++,当然也可以站在没有学会用C++的人的立场,说C++太过于复杂,以至大多数人是学不会用不好的,但它的每个设计的确都有它的现实考虑,编程语言是很实在的东西,往往外貌冷冰冰但其为什么是这样有充足原因。
二、你的好运气
你要是明白函数调用时局部变量是如何入栈出栈的,看看反汇编的代码,并跟踪一下堆栈的变化情况,你会设计出一个让值产生变化的例子。如果这类错误后,导致被改变的值,并不是指针的值,则在这么小的程序中,系统不一定都崩溃,它不过是让部分你没照顾到的地方变了变值,却没有影响输出。
建议楼主阅读一下TCPL有关临时变量一节,看看各种条件下生成的临时变量的作用域,与给出名字的局部变量间,有何差同。
三、其他一些为什么的例子
关于C++的为什么特别多,如果你不是经验丰富且善于思考,是很难理解为什么有这么多为什么的。当然,为什么的多少,是个程度问题,有差异存在的地方就有程度问题,不同的人善用不同的东西,C++是“小众”的,但还不至于只是几个人的,毕竟TIOBE还排第3。
1.operator重载的解析顺序为什么如现在标准那样设计?是权衡了使用者的方便,和编译器的效率之间的一种平衡,它过度自由带来的是呈指数级上升的编译时开销,且该开销并不一定值得。
2.内置数组,为什么不设置下标检测?如果检测下标,定然就会在每次访问下标时,做是否越界的检验,这就带来了运行时开销。如果你的算法非常好,定然不需要检测下标,则语言假定一定要在每次访问下标时都判断,就会影响效率并失去选择的机会。如果设置N个选项,可以用来关闭或打开是否检测下标,那不应该是一种语言应该干的,各有各的侧重点。
3.C语言传数组参数为什么默认是转换成指针类型?以C语言产生那个年代的硬件条件,复制数组很奢侈,尤其函数被调用往往很频繁,算法要尽量往不复制的情况下设计,如果实在必要,非要复制,你也可以手动memcpy嘛!总之它不是默认项。C++给了用户另一种选项,即通过加上引用,而使得能够真正传整个数组,不过这都是很多年以后的事了。
4.for语句为什么有的灵活有的严格?像在Ada中的语法,便是禁止循环变量被改变,且不能设置步长值,要想达到这两个目的,便只能用其他变量再过渡,这样做是为了高度的安全。反之,C语言的for则非常灵活,也没有Ada那么多的限制,但这种灵活并不能保证用户用其写出错误逻辑的代码;VB的自由度则介于二者之间。不能因为这些语言的设计不同,而指责其中某一种语言为何不对某一语法特性做必要的限制,它真的必要吗?个案好说,但综合全局,很难评估。
四、设计者们不傻
且任何有影响力的技术,其规范,都是经过全球大量从业者多年实践后,总结整理并论证出来的,并不是一个或几个人拍拍脑袋就草率决定的,因此C++的新标准化过程要历时8年之久。Bjarne Stroustrup不傻,Herb Sutter, Stanley Lippman, Scott Meyer, Alexander Stepanov, Andrew Koenig等人也不傻,标准委员会都不是白给的,大多数细节问题早就被提出过。具体实现,要难得多,这点语法层面上的皮毛问题,都不值一提。
暂且写这么多吧。
原文出自【比特网】,转载请保留原文链接:http://soft.chinabyte.com/database/353/11894853.shtml
我一直很纳闷,既然所有C++权威的书上都要求“一定不要返回局部对象或变量的引用和指针”,那为什么C++编译器不从语法上直接禁掉这种用法,让你编译通不过(在技术上应该不难实现的)。如果只是建议的话,那么“返回局部对象或变量的引用和指针”是否有用武之地呢?(从理论上来讲,我认为这种做法似乎总是错误的,原因大家都知道。)
EX(1)
#include
using namespace std;
class CComplex
{
public:
CComplex():real(0),image(0){}
CComplex(double real,double image):real(real),image(image){}
CComplex& operator+(const CComplex& second)
{
CComplex temp(real+second.real,image+second.image);
return temp;
}
void Print()
{
cout《"("《real《"+"《image《"i)"《endl;
}
private:
double real;
double image;
};
int main()
{
CComplex a(2,4);
CComplex b(1.5,3.5);
CComplex c=a+b;
c.Print();
return 0;
}
operator+返回的是临时对象的引用,为什么能正确地工作???
EX(2)
#include
#include
using namespace std;
string& f()
{
string s("hello");
return s;
}
int main()
{
cout《f()《endl;
return 0;
}
同样是对象,为什么string对象就不行,就因为string比较特殊???
EX(3)
#include
#include
using namespace std;
double& f()
{
double d(5.55);
return d;
}
int main()
{
cout《f()《endl;
return 0;
}
为什么内置类型(int,float等均可)返回局部变量的引用总可以正确地工作???
这个问题似乎以前已经有人讨论过,但一直没有定论。
不要跟我说运行正确是因为我运气好,运气不好地话就输出任意值;
我运行了N次,未见任何异常,也不要说运行上千万次才有可能出问题;
我在GCC和VS2010上都验证过了,我觉得是不是编译器做了相应的优化啊(特别是针对内置基本类型)。
有想法的兄弟望赐教,感激不尽!!
int main()
{
CComplex a(2,4);
CComplex b(1.5,3.5);
CComplex c=a+b;
c.Print();
return 0;
}
operator+返回的是临时对象的引用,为什么能正确地工作???
答:main函数在执行之后,a,b入栈,接着a+b调用了operator+,temp也入栈,operator+执行完后,temp出栈并调用析构函数,由于出栈仅仅是移动了PC指针,而你又未写析构函数将CComplex清零,因此temp所占的那块栈空间的内存依然保持原样,只是PC指针已经不再指向它,而operator+返回的引用其实指向的是temp所占内存,然后在调用CComplex的默认拷贝构造的函数的时候,由于拷贝构造函数的输入参数也是引用,因此也指向temp那块内存,对此快内存也会按照CComplex类型来进行访问,最后c就得到了temp的内容。这里即使是写成CComplex& c=a+b;结果也是能输出temp的内容的。此时你若在此句话后面再加几个函数调用,这些函数必须要有参数或内部定义有变量,然后再c.Print(),你会发现结果完全变了。
EX(2)
#include
#include
using namespace std;
string& f()
{
string s("hello");
return s;
}
int main()
{
cout《f()《endl;
return 0;
}
同样是对象,为什么string对象就不行,就因为string比较特殊???
答:因为s在出栈的时候其析构函数会将内存都清掉,在外面还想访问自然访问不成功了。
EX(3)
#include
#include
using namespace std;
double& f()
{
double d(5.55);
return d;
}
int main()
{
cout《f()《endl;
return 0;
}
答:理解了上面两个答案,这个我就不用多说了吧。
每个人必有其背后的深刻原因,只是受限于种种因素,人们不可能都去搞明白。更多时候,并不是原因不充分,只是人们以其自己的知识背景还不足以理解。
一、为什么不禁用的问题
为什么不禁引用返回局部变量,技术上真的是不难吗?且,有足够的必要吗?请见以下例子:
int *f1(int &ri)
{
return &ri;
}
int *f2
{
int i=4;
int *j;
j=f1(i);
return j;
}
int main()
{
int *p=f2;
*p=6;
return 0;
}
p在初始化后,*p生命期是否已经结束了呢?我相信,如果这件事也得由编译器去判断,那么显然,程序员全部可以下岗了,编译器实在是太智能了,人还有必要存在吗?但现有技术真的能吗?如果能的话,要花多大开销,这个开销有必要吗?“千万不要返回局部对象或变量的引用和指针”应该是个原则性的东西,它是个典型代表,其实大原则是“不要在自动变量(不管是表达式中间结果的临时变量(如果它不能保证总优化到寄存器中)还是源程序中有明确名字的auto变量)生命期结束后还试图解引用它”。
程序设计语言课一般会说语言的可写性与可读性是对矛盾,C语言的可写性特别强,既会给比较强的人非常灵活的选择,又会让入门者走不少弯路或者半途而废。利器不是谁都能用得好,这与水平不水平没什么关系,说人的水平不足够使用C++,当然也可以站在没有学会用C++的人的立场,说C++太过于复杂,以至大多数人是学不会用不好的,但它的每个设计的确都有它的现实考虑,编程语言是很实在的东西,往往外貌冷冰冰但其为什么是这样有充足原因。
二、你的好运气
你要是明白函数调用时局部变量是如何入栈出栈的,看看反汇编的代码,并跟踪一下堆栈的变化情况,你会设计出一个让值产生变化的例子。如果这类错误后,导致被改变的值,并不是指针的值,则在这么小的程序中,系统不一定都崩溃,它不过是让部分你没照顾到的地方变了变值,却没有影响输出。
建议楼主阅读一下TCPL有关临时变量一节,看看各种条件下生成的临时变量的作用域,与给出名字的局部变量间,有何差同。
三、其他一些为什么的例子
关于C++的为什么特别多,如果你不是经验丰富且善于思考,是很难理解为什么有这么多为什么的。当然,为什么的多少,是个程度问题,有差异存在的地方就有程度问题,不同的人善用不同的东西,C++是“小众”的,但还不至于只是几个人的,毕竟TIOBE还排第3。
1.operator重载的解析顺序为什么如现在标准那样设计?是权衡了使用者的方便,和编译器的效率之间的一种平衡,它过度自由带来的是呈指数级上升的编译时开销,且该开销并不一定值得。
2.内置数组,为什么不设置下标检测?如果检测下标,定然就会在每次访问下标时,做是否越界的检验,这就带来了运行时开销。如果你的算法非常好,定然不需要检测下标,则语言假定一定要在每次访问下标时都判断,就会影响效率并失去选择的机会。如果设置N个选项,可以用来关闭或打开是否检测下标,那不应该是一种语言应该干的,各有各的侧重点。
3.C语言传数组参数为什么默认是转换成指针类型?以C语言产生那个年代的硬件条件,复制数组很奢侈,尤其函数被调用往往很频繁,算法要尽量往不复制的情况下设计,如果实在必要,非要复制,你也可以手动memcpy嘛!总之它不是默认项。C++给了用户另一种选项,即通过加上引用,而使得能够真正传整个数组,不过这都是很多年以后的事了。
4.for语句为什么有的灵活有的严格?像在Ada中的语法,便是禁止循环变量被改变,且不能设置步长值,要想达到这两个目的,便只能用其他变量再过渡,这样做是为了高度的安全。反之,C语言的for则非常灵活,也没有Ada那么多的限制,但这种灵活并不能保证用户用其写出错误逻辑的代码;VB的自由度则介于二者之间。不能因为这些语言的设计不同,而指责其中某一种语言为何不对某一语法特性做必要的限制,它真的必要吗?个案好说,但综合全局,很难评估。
四、设计者们不傻
且任何有影响力的技术,其规范,都是经过全球大量从业者多年实践后,总结整理并论证出来的,并不是一个或几个人拍拍脑袋就草率决定的,因此C++的新标准化过程要历时8年之久。Bjarne Stroustrup不傻,Herb Sutter, Stanley Lippman, Scott Meyer, Alexander Stepanov, Andrew Koenig等人也不傻,标准委员会都不是白给的,大多数细节问题早就被提出过。具体实现,要难得多,这点语法层面上的皮毛问题,都不值一提。
暂且写这么多吧。
原文出自【比特网】,转载请保留原文链接:http://soft.chinabyte.com/database/353/11894853.shtml