【问题标题】:Unexpected const reference behavior意外的 const 引用行为
【发布时间】:2011-03-16 00:24:18
【问题描述】:
#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

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

【问题讨论】:

  • 我不明白为什么这不应该编译。 FWIW,Comeau 同意我的观点。
  • @sbi:奇怪的是,Comeau 拒绝了禁用 C++0x 扩展的代码,但接受了启用了 C++0x 扩展的代码。
  • g++ 4.4.2,FWIW 没有错误。
  • gcc 4.5 接受带有和不带有 C++0x 扩展的代码。
  • @James McNellis:在 C++03 中,当从右值初始化 const 引用时,允许实现从右值构造一个临时并将引用绑定到该临时(允许这样做递归!)或直接绑定到右值。无论使用与否,复制构造函数都必须是可访问的。 C++0x 重新审视了这一点,如果右值是引用兼容的,则引用必须直接绑定到右值;不再允许使用临时对象,因此对拥有可用复制构造函数的限制也已取消。

标签: c++ gcc4


【解决方案1】:

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

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

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

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

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

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

请注意,这在 C++0x 中已更改,分辨率为 CWG defect 391。新语言如下(N3092 §8.5.3):

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

-- 初始化表达式是一个右值,“cv1 T1”与“cv2 T2”是引用兼容的,

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

那么引用在第一种情况下绑定到初始化表达式右值,在第二种情况下绑定到作为转换结果的对象(或者在任何一种情况下,绑定到对象的适当基类子对象)。

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

【讨论】:

  • 分辨率说明/标准谈到了子对象。显然,子对象具有多态性,这让我有点困惑。出于好奇,该标准是否会在某处缩小范围?我在使用的 2005 工作副本中找不到任何内容,但我想我只是不知道要搜索什么。我正在查看“标准转换”和有关复制构造函数的部分。
  • @Zachary:C++ 标准的一个“特点”是主题通常分布在六个不同的部分,如果您不熟悉可能的位置,则很难在其中找到信息.在这种情况下,声明器初始化器(第 8.5 节)的子条款很重要,因为您正在初始化引用。关于临时对象的 §12.2 很重要,因为您有一个临时类类型对象。通常,重要的部分是段落中间的一个简短的句子。有时我发现搜索关键字(例如“reference”)会有所帮助。
【解决方案2】:

所以你使用的是“复制初始化”:

8.5/11 初始化器

初始化的形式(使用 括号或 =) 通常是 微不足道,但在什么时候很重要 被初始化的实体有一个 班级类型;见下文。 ...

初始化发生在 参数传递,函数返回, 抛出异常(15.1),处理 例外 (15.3),以及 大括号括起来的初始值设定项列表 (8.5.1) 称为复制初始化 并且等价于形式

T x = a;

在 new 中发生的初始化 表达式 (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 参考资料说:

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

  • 引用绑定到右值表示的对象(参见 3.10)或其中的子对象 那个对象。

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

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

我最初阅读最后一句(“将使用的构造函数......”)适用于这两个选项,但也许它应该被理解为仅适用于 seconds 选项 - 或者至少也许 GCC 就是这样维护人员正在阅读它。

我不确定这是否是 GCC 版本的不同行为之间发生的情况(欢迎使用 cmets)。我们肯定达到了我的语言律师技能的极限......

【讨论】:

  • 被初始化的实体不是对象,而是引用。
  • 这也是我的第一个想法,但它一定是编译器错误,否则更高版本的 gcc 几乎不会改变行为。
  • @Michael Burr:我不明白为什么直接与复制初始化对引用很重要。 8.5/11 说“见下文;” 8.5/14 的第一个项目符号(即“下面”)说“如果目标类型是引用类型,请参阅 8.5.3”,并且 8.5.3 不区分直接初始化和复制初始化。
  • 8.5.3/5 讨论了这一点,并确实表明对于绑定到临时的引用“无论复制是否实际完成,用于制作复制的构造函数都应该是可调用的”
  • @Michael Burr:对。但是,如果我们正在初始化一个对象,那么直接初始化和复制初始化都需要一个可访问的复制构造函数。为什么直接初始化引用不需要可访问的复制构造函数?
【解决方案3】:

我觉得确实是编译器的bug,gcc好像觉得是拷贝初始化。改用直接初始化:

const A& b(B());

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-03
    • 2020-09-18
    • 1970-01-01
    • 2015-10-28
    • 2016-04-20
    • 1970-01-01
    相关资源
    最近更新 更多