Chinaunix

标题: [结贴]如果const T&是只读引用,那为什么T t(T())是个函数声明而不是对象实例化? [打印本页]

作者: cdsfiui    时间: 2015-03-04 09:29
标题: [结贴]如果const T&是只读引用,那为什么T t(T())是个函数声明而不是对象实例化?
本帖最后由 cdsfiui 于 2015-03-04 16:14 编辑

前两天的一个帖子是讨论了:
const T&可以绑定左值,也可以绑定右值(以及字面常量),例如:

  1. void f(const int& i){}
  2. int main(void)
  3. {
  4.     int i = 1;
  5.     f(i);
  6.     f(2);
  7.     const int&r1 = i;
  8.     const int&r2 = 2;
  9.     return 0;
  10. }
复制代码
这都没有问题,那么为什么下面的代码(来自本版另一个帖子)

  1. class X
  2. {
  3. public:
  4.     X(int i){ cout << __FUNCTION__ << endl; }
  5.     X(){ cout << __FUNCTION__ << endl; }
  6.     X(const X&){ cout << __FUNCTION__ << endl; }
  7.     X(X&&){ cout << __FUNCTION__ << endl; }
  8. };
  9. int main(void)
  10. {
  11.     X x(X());
  12.     return 0;
  13. }
复制代码
main函数当中的X x(X())被编译器认为是一个函数声明,而不是一个x变量的实例呢? 如果const X&可以绑定X()这样的临时变量(右值),为什么不是用X的拷贝构造函数,构造了一个x?
之前有一位大侠指出如果写成X x((X())),就是调用了一个默认对象的构造。但是这也不是我期待的:这样改的效果,似乎是一个函数声明加一个默认构造,而没有我期待的拷贝构造。

我希望知道语法上的原因,为什么这里X x(X())是一个函数声明?
非常感谢。。。。。。。。

作者: www1862    时间: 2015-03-04 10:32
拷贝构造函数的参数是自身类类型的引用吧?
作者: cdsfiui    时间: 2015-03-04 10:59
www1862 发表于 2015-03-04 10:32
拷贝构造函数的参数是自身类类型的引用吧?


是啊。
不过,这能帮助解释我的问题么?
作者: bruceteen    时间: 2015-03-04 12:03
为什么这里X x(X())是一个函数声明?
----- x是函数名,返回类型是X,参数是一个省略掉形参名的函数(这个函数的返回类型为X)

对于函数声明 int fun( int param() ) 你能看得懂吗?
再省略形参名后为 int fun( int() ) 你能看得懂吗?
把类型int换成类型X后为 X fun( X() ) 你就看不懂了?
作者: www1862    时间: 2015-03-04 12:25
楼主的意思是 X x();//生成了一个名为x的X对象。
X x( X() );//这里的X()楼主认为是一个无名对象。然后用这个无名对象用拷贝构造的方式生成一个名为x的X对象。
作者: bruceteen    时间: 2015-03-04 12:43
回复 5# www1862
X x();//生成了一个名为x的X对象。
------ 不是吧,这里同样是函数声明,x同样只是个函数名
作者: windoze    时间: 2015-03-04 14:43
lz一共提了两个问题

1. X x(X())被当作声明这是没有办法的事,C++语法在这种地方上存在二义性,X x(X()),你可以解释成一个叫x的函数原型,它返回X类型,接受一个“X()”,这东西其实是一个函数类型,没有参数,返回X。
编译器处理这类东西的原则大致是,如果这个东西可以被解释成声明,那它就是声明,所以它就变成声明了。

2. 在X()外面加一个括号之后,X x((X()))就不是一个有效的函数原型声明,所以它就被解释成“X x(一个默认构造的临时X对象)”,这个构造函数会匹配“X(X&&)”,尽管它可以匹配“X(const X&)”,但C++标准规定临时对象优先匹配右值引用,所以会调用move constructor而不是copy constructor。
rvalue ref本来就是为了这个原因才引入的,也就是说,move/rref之类的东西的引入最初就是为了区分函数参数是一个临时对象(对象的生存期不超过表达式)还是其他情况,其它所有的用法都是围绕这个初衷的完整性补丁。你用临时对象作为参数调用构造函数当然会使用move construtor而不是copy constructor
作者: cdsfiui    时间: 2015-03-04 15:08
windoze 发表于 2015-03-04 14:43
lz一共提了两个问题

1. X x(X())被当作声明这是没有办法的事,C++语法在这种地方上存在二义性,X x(X()) ...


谢谢,但是残酷的事实是,以下代码:

  1. #include<iostream>
  2. using namespace std;
  3. class X
  4. {
  5. public:
  6.     X(int i){ cout << __FUNCTION__ << endl; }
  7.     X(){ cout << "default ctor" << endl; }
  8.     X(const X&){ cout << "copy constructor" << endl; }
  9.     X(X&&){ cout << "move constructor" << endl; }
  10. };
  11. int main(void)
  12. {
  13.     X x((X()));
  14.     return 0;
  15. }
复制代码
我在最新的gcc/vc下编译运行,都是打印了"default ctor"。
并没有调用大侠你所期待的move constructor,也没有我所期待的,早构造玩一个临时对象后,还要加上一个copy ctor才算x构造完毕,因为这里是从一个临时对象来构建x,要么是move,要么是copy。

可是,真的只是打印了默认的ctor。

还请大侠继续解释一下!!!!!!
作者: windoze    时间: 2015-03-04 15:38
回复 8# cdsfiui

这是RVO……
C++有一大堆标准框架内的优化方案,你写的代码刚好就是其中的一种情况(什么?你问我为神马随手写段代码就碰上特殊情况?要是不能把你随手写出来的代码优化掉你以为C++标准为神马会复杂的谁也搞不清?)

作者: cdsfiui    时间: 2015-03-04 16:14
windoze 发表于 2015-03-04 15:38
回复 8# cdsfiui

这是RVO……


大侠你的回答不但有水平,而且有激情,我很喜欢。结贴啦。
作者: bruceteen    时间: 2015-03-04 16:25
是RVO,只要在 X(X&&) 前加个 private: 就会编译出错,说明此处是由  X(X&&)  的访问控制所控制。

但我想问的时,老的VC是有bug的,加了private:也能编译通过,不知道最新的VC是否改掉了这个bug
作者: www1862    时间: 2015-03-04 17:36
本帖最后由 www1862 于 2015-03-04 18:12 编辑

什么是RVO?优化掉了?
X x( (x()) ); 等价于 X x();了?
作者: windoze    时间: 2015-03-04 23:06
回复 12# www1862

差不多就是这样,这个优化就是让程序少做一次copy/move construct,内层函数返回的对象直接构造在外面
X x((X()))优化之后基本等价于X x(),类似的X x((X(args)))基本等价于X x(args)

作者: jianziy    时间: 2015-03-05 09:11
4楼正解 ....最不喜欢不熟悉C/C++语法的情况下,挖坑自己跳下去还要别人来捞你....




欢迎光临 Chinaunix (http://bbs.chinaunix.net/) Powered by Discuz! X3.2