免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
123下一页
最近访问板块 发新帖
查看: 9364 | 回复: 25

关于c++函数返回类的复制构造问题 [复制链接]

论坛徽章:
0
发表于 2010-10-16 11:07 |显示全部楼层
如题,再看“有闲人对设计新的语言感兴趣的么?一起来讨论一下吧。”这个帖子的时候,gtkmm举了如下的例子
  1. class T
  2. {
  3.     public:
  4.         T(){}
  5.         T(const T&){
  6.         throw 0;
  7.         }
  8. };

  9. T test( int i)
  10. {
  11.     T t1,t2;
  12.     return t1;
  13.     return t2;
  14. }

  15. int main(int argc, char *argv[])
  16. {
  17.     T t = test(0);
  18.     return 0;
  19. }
复制代码
试了下,确实如果有两个return,就会调用复制构造函数,导致抛出异常终止程序。但只有一个的话,就正常。然后查了下,原来g++在用函数返回值初始化一个类的时候,倾向于不调用复制构造函数,而是直接把函数中的局部变量当成新的变量用,具体到这里,可以看到当只有一个return存在的时候,test中的局部变量 t1 和main中的 t 地址是一样的。通过比较地址和查看汇编码,看来这种情况下,main创建 t 并隐式的把对 t 的引用传递给 test ,并在test中当 t1 用(换句话说test中的 t1 就是对 main中的 t 的引用),所以避免了复制构造函数的调用(我之前以为是test返回的时候把对t1的引用返给main,但显然不对,test结束局部变量t1就没有意义了)

问题是为什么多一个return就会导致不使用这种优化行为?明明那个return不会被执行?

论坛徽章:
0
发表于 2010-10-16 11:55 |显示全部楼层
优化本来就是一个很容易出错的地方,设定一个优化策略,有可能对某些特殊写法会出错。

先看这个策略:
如果函数的返回值是一个对象,而且所有的return语句都是返回同一个局部变量,
那么把这个局部变量跟函数的返回值直接绑定(绑定的细节是另一个问题了),
那个这个优化应该是可行的,而且判断起来也比较安全。

可以测试一下,多个return语句,但都return同一个局部对象,也是会被优化掉的。

至于另一个策略:
return t1; 后面的 return t2; 这个return是否多余,前一个return是否多余,是否可以去掉,
这个就有待商榷,可能编译器不敢去优化。

即使都优化,那么哪个先优化也是问题,如果前一个先优化,那么后面即使去掉了,前面还不知道

论坛徽章:
0
发表于 2010-10-16 18:41 |显示全部楼层
优化本来就是一个很容易出错的地方,设定一个优化策略,有可能对某些特殊写法会出错。

先看这个策略:
...
drangon 发表于 2010-10-16 11:55



    确实是这样,谢谢了

论坛徽章:
2
青铜圣斗士
日期:2015-11-26 06:15:59数据库技术版块每日发帖之星
日期:2016-07-24 06:20:00
发表于 2010-10-17 13:42 |显示全部楼层
回复 1# liwangli1983

举2个例子。

  1. char* s = "read only";
  2. *s = 0;
复制代码
这个错误很明显。

  1. execl( ..., 0 );
复制代码
这个错误, 就这几天就有人发帖在说。

这些错误可以从各个角度去解释。

从C语言来说
1. string literal是不可写的

所以, 对s进行写操作是违反C语言规定的。

2. 可变长参数的省略部分仅仅执行默认实参提升
而不会进行隐式类型转换, 因为根本没有类型可供检查

所以, 传递0, 或者 NULL不一定是程序员的意图
最保险的是 (void*)0。


从C++角度来说。
编写对下面两种方式不敏感的代码
X x = X(p);
X x(p);

就是C++的规则之一。


当然, 违反这些规则, 从编译选项、 32/64的区别、 编译优化的区别的角度, 也不是不行。
比较累……

论坛徽章:
0
发表于 2010-10-17 14:33 |显示全部楼层
只有唯一返回值时,GCC才优化掉复制构造函数。
而VC则始终不优化。


我说这个例子只是想证明C++不是一个“完备”的语言。
class应该像java,C#,d一样,直接在堆里,有内存管理的。
struct则和C一样。

论坛徽章:
0
发表于 2010-10-17 17:28 |显示全部楼层
回复 5# gtkmm


不知道你说的“完备”是什么意思,这分明就是 C++ 标准明文规定可以进行的优化(C++03 12.8/15),不是某个编译器擅做主张的结果。

所以不要依赖复制构造函数产生的边缘效应,这应当是个常识……

论坛徽章:
0
发表于 2010-10-18 13:12 |显示全部楼层
回复  liwangli1983

举2个例子。这个错误很明显。这个错误, 就这几天就有人发帖在说。

这些错误可以 ...
OwnWaterloo 发表于 2010-10-17 13:42


那个例子(双return)就是对两种形式敏感的代码了吧

论坛徽章:
0
发表于 2010-10-18 13:18 |显示全部楼层
好吧, 那就当这是C++的标准规定。

可是一个语言的标准,是这样的高深难懂,也可以算是“标准”的一个BUG了。
而不是语言的问题了。

论坛徽章:
0
发表于 2010-10-18 14:45 |显示全部楼层
回复 8# gtkmm


这恰恰不是 bug,而是有意为之。标准里面的措辞很明确,即使对象的赋值构造函数和析构函数会产生边缘效应也允许这个优化。这个优化的原因也很简单,返回重量级对象的代价太大了,有了这个优化效率会好上很多。看看 C++0x 里面几乎专门为优化临时变量而增加的右值引用语法就知道,这是 C++ 委员会一直关注的一个效率问题。

而且,只要保证复制构造函数符合复制的传递性语义,这个优化就不会产生语义上的问题。复制操作要符合语义,不然就应当禁止或回避复制,我觉得这不是一个多么苛刻的要求,而是设计类时最基本的要求。把中间的临时变量优化掉,我也看不出这是一个多么高深的规定。充其量是这个规定很少被提起,以至于没有多少人知道而已。

论坛徽章:
2
青铜圣斗士
日期:2015-11-26 06:15:59数据库技术版块每日发帖之星
日期:2016-07-24 06:20:00
发表于 2010-10-18 14:53 |显示全部楼层
回复 7# liwangli1983

问题出在构造上。

X x; // 不抛出
X x = X(); // 抛出

双return是使编译器改变优化策略的一种方式。
一旦代码陷入与具体的编译器行为有关, 就很麻烦了。

这个例子太无理取闹, 看不出有什么实际意义。 否则还可以进一步讨论。

如果要禁止复制:
1. 让复制构造是private可以在编译时发现
2. 不给出复制构造的定义, 可以在链接时发现
3. throw 是运行时发现
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP