繁体   English   中英

意外的const引用行为

[英]Unexpected const reference behavior

#include <iostream>

class A { 
  public:  
    A(){ cerr << "A Constructor" << endl; }  
    ~A(){ cerr << "A Destructor" << endl; }  
    A(const A &o){ cerr << "A Copy" << endl; } 
    A& operator=(const A &o){ cerr << "A Assignment" << endl; return *this; }
};


class B : public A { 
  public:  
    B() : A() { cerr << "B Constructor" << endl; }  
    ~B(){ cerr << "B Destructor" << endl; }
  private:
    B(const B &o) : A() { cerr << "B Copy" << endl; } 
    B& operator=(const B &o){ cerr << "B Assignment" << endl; return *this; }
};

int main() {  
  A a;  
  const A &b = B();  
  return 0; 
}

在GCC 4.2中,我收到此消息:

In function 'int main()':
Line 16: error: 'B::B(const B&)' is private
compilation terminated due to -Wfatal-errors.

如果我从B中删除“私有”,我得到我期望的输出:

A Constructor
A Constructor
B Constructor
B Destructor
A Destructor
A Destructor

我的问题是:为什么制作一个不被称为私有的方法会改变这个代码是否编译? 这是标准规定的吗? 有解决方法吗?

当前标准(C ++ 03)中的重要措辞似乎在§8.5.3中,它解释了如何初始化引用(在这些引号中, T1是被初始化的引用的类型, T2是初始化器的类型)表达)。

如果初始化表达式是rvalue, T2是类类型,并且“ cv1 T1 ”与“ cv2 T2 ”引用兼容,则引用cv2 T2下列方式之一绑定(选择是实现定义的):

- 引用绑定到由rvalue(参见3.10)表示的对象或该对象内的子对象。

- 创建临时类型为“ cv1 T2 ”[sic],并调用构造函数将整个右值对象复制到临时对象中。 引用绑定到临时或临时内的子对象。

无论副本是否实际完成,用于制作副本的构造函数都应该是可调用的。

因此,即使实现将引用直接绑定到临时对象,也必须可以访问复制构造函数。

请注意,根据CWG缺陷391的分辨率,这在C ++ 0x中已更改。 新语言为(N3092§8.5.3):

否则,如果T2是类类型而且

- 初始化表达式是rvalue,“ cv1 T1 ”与“ cv2 T2 ”引用兼容

- T1T2无参考相关,初始化表达式可以隐式转换为“ cv3 T3"类型的右值(通过枚举适用的转换函数(13.3.1.6)并通过过载选择最佳转换函数来选择此转换)决议(13.3)),

然后,引用绑定到第一种情况下的初始化表达式rvalue和第二种情况下转换结果的对象(或者,在任何一种情况下,绑定到对象的相应基类子对象)。

第一种情况适用,引用直接“绑定”到初始化表达式。

所以你正在使用的是'复制初始化':

8.5 / 11初始化器

初始化的形式(使用括号或=)通常是无关紧要的,但在初始化的实体具有类类型时确实很重要; 见下文。 ...

参数传递,函数返回,抛出异常(15.1),处理异常(15.3)和大括号括起初始化列表(8.5.1)时发生的初始化称为复制初始化,相当于表单

 T x = a; 

新表达式(5.3.4),static_cast表达式(5.2.9),功能表示法类型转换(5.2.3)以及基本和成员初始化程序(12.6.2)中发生的初始化称为直接初始化,相当于表格

 T x(a); 

在13.3.1.3“按构造函数初始化”中,所选构造函数的重载是:

当类类型的对象被直接初始化(8.5),或者从相同或派生类类型(8.5)的表达式进行复制初始化时,重载决策选择构造函数。 对于直接初始化,候选函数是正在初始化的对象的类的所有构造函数。 对于复制初始化,候选函数是该类的所有转换构造函数(12.3.1)。

因此,对于复制初始化,复制构造函数必须可用。 但是,允许编译器“优化掉”副本:

12.2 / 1临时物体

即使在避免创建临时对象时(12.8),也必须遵守所有语义限制,就像创建临时对象一样。 [示例:即使未调用复制构造函数,也应满足所有语义限制,例如可访问性(第11节)。 ]

您可以通过避免复制初始化和使用直接初始化来获得所需的效果:

 const A &b(B());  

注意:

由于较新版本的GCC显然有不同的行为,我想我会发布这个注释,这可能会解决差异(两种行为仍符合标准):

8.5.3 / 5参考文献说:

如果初始化表达式是rvalue,T2是类类型,并且“cv1 T1”与“cv2 T2”引用兼容,则引用将以下列方式之一绑定(选择是实现定义的):

  • 引用绑定到由rvalue(参见3.10)表示的对象或该对象内的子对象。

  • 创建临时类型为“cv1 T2”[sic],并调用构造函数将整个右值对象复制到临时对象中。 引用绑定到临时或临时内的子对象。

无论副本是否实际完成,用于制作副本的构造函数都应该是可调用的。

我最初阅读最后一句(“将要使用的构造函数......”)来应用于这两个选项,但也许应该只读取应用于秒选项 - 或者至少可能是GCC维护者正在阅读的内容它。

我不确定GCC版本的不同行为之间是否存在这种情况(欢迎评论)。 我们肯定达到了我的语言 - 律师技能的极限......

我认为这确实是一个编译器bug,gcc似乎认为是复制初始化。 使用直接初始化代替:

const A& b(B());

复制初始化中的复制构造函数调用总是被优化掉(复制省略的实例),然后不必可用。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM