简体   繁体   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; 
}

In GCC 4.2, I get this message: 在GCC 4.2中,我收到此消息:

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

If I remove the "private" from B, I get the output I expect: 如果我从B中删除“私有”,我得到我期望的输出:

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

My question is: why does making a method which isn't called private change whether this code compiles? 我的问题是:为什么制作一个不被称为私有的方法会改变这个代码是否编译? Is this standard-mandated? 这是标准规定的吗? Is there a workaround? 有解决方法吗?

The important verbiage in the current standard (C++03) seems to be in §8.5.3, which explains how references are initialized (In these quotes, T1 is the type of the reference being initialized and T2 is the type of the initializer expression). 当前标准(C ++ 03)中的重要措辞似乎在§8.5.3中,它解释了如何初始化引用(在这些引号中, T1是被初始化的引用的类型, T2是初始化器的类型)表达)。

If the initializer expression is an rvalue, with T2 a class type, and " cv1 T1 " is reference-compatible with " cv2 T2 ," the reference is bound in one of the following ways (the choice is implementation-defined): 如果初始化表达式是rvalue, T2是类类型,并且“ cv1 T1 ”与“ cv2 T2 ”引用兼容,则引用cv2 T2下列方式之一绑定(选择是实现定义的):

-- The reference is bound to the object represented by the rvalue (see 3.10) or to a sub-object within that object. - 引用绑定到由rvalue(参见3.10)表示的对象或该对象内的子对象。

-- A temporary of type " cv1 T2 " [sic] is created, and a constructor is called to copy the entire rvalue object into the temporary. - 创建临时类型为“ cv1 T2 ”[sic],并调用构造函数将整个右值对象复制到临时对象中。 The reference is bound to the temporary or to a sub-object within the temporary. 引用绑定到临时或临时内的子对象。

The constructor that would be used to make the copy shall be callable whether or not the copy is actually done. 无论副本是否实际完成,用于制作副本的构造函数都应该是可调用的。

So, even if the implementation binds the reference directly to the temporary object, the copy constructor must be accessible. 因此,即使实现将引用直接绑定到临时对象,也必须可以访问复制构造函数。

Note that this is changed in C++0x, per the resolution of CWG defect 391 . 请注意,根据CWG缺陷391的分辨率,这在C ++ 0x中已更改。 The new language reads (N3092 §8.5.3): 新语言为(N3092§8.5.3):

Otherwise, if T2 is a class type and 否则,如果T2是类类型而且

-- the initializer expression is an rvalue and " cv1 T1 " is reference-compatible with " cv2 T2 ," - 初始化表达式是rvalue,“ cv1 T1 ”与“ cv2 T2 ”引用兼容

-- T1 is not reference-related to T2 and the initializer expression can be implicitly converted to an rvalue of type " cv3 T3" (this conversion is selected by enumerating the applicable conversion functions (13.3.1.6) and choosing the best one through overload resolution (13.3)), - T1T2无参考相关,初始化表达式可以隐式转换为“ cv3 T3"类型的右值(通过枚举适用的转换函数(13.3.1.6)并通过过载选择最佳转换函数来选择此转换)决议(13.3)),

then the reference is bound to the initializer expression rvalue in the first case and to the object that is the result of the conversion in the second case (or, in either case, to the appropriate base class subobject of the object). 然后,引用绑定到第一种情况下的初始化表达式rvalue和第二种情况下转换结果的对象(或者,在任何一种情况下,绑定到对象的相应基类子对象)。

The first case applies and the reference is "bound directly" to the initializer expression. 第一种情况适用,引用直接“绑定”到初始化表达式。

So what you're using is 'copy-initialization': 所以你正在使用的是'复制初始化':

8.5/11 Initializers 8.5 / 11初始化器

The form of initialization (using parentheses or =) is generally insignificant, but does matter when the entity being initialized has a class type; 初始化的形式(使用括号或=)通常是无关紧要的,但在初始化的实体具有类类型时确实很重要; see below. 见下文。 ... ...

The initialization that occurs in argument passing, function return, throwing an exception (15.1), handling an exception (15.3), and brace-enclosed initializer lists (8.5.1) is called copy-initialization and is equivalent to the form 参数传递,函数返回,抛出异常(15.1),处理异常(15.3)和大括号括起初始化列表(8.5.1)时发生的初始化称为复制初始化,相当于表单

 T x = a; 

The initialization that occurs in new expressions (5.3.4), static_cast expressions (5.2.9), functional notation type conversions (5.2.3), and base and member initializers (12.6.2) is called direct-initialization and is equivalent to the form 新表达式(5.3.4),static_cast表达式(5.2.9),功能表示法类型转换(5.2.3)以及基本和成员初始化程序(12.6.2)中发生的初始化称为直接初始化,相当于表格

 T x(a); 

In 13.3.1.3 "Initialization by constructor", the overloads for the constructor chosen are: 在13.3.1.3“按构造函数初始化”中,所选构造函数的重载是:

When objects of class type are direct-initialized (8.5), or copy-initialized from an expression of the same or a derived class type (8.5), overload resolution selects the constructor. 当类类型的对象被直接初始化(8.5),或者从相同或派生类类型(8.5)的表达式进行复制初始化时,重载决策选择构造函数。 For direct-initialization, the candidate functions are all the constructors of the class of the object being initialized. 对于直接初始化,候选函数是正在初始化的对象的类的所有构造函数。 For copy initialization, the candidate functions are all the converting constructors (12.3.1) of that class. 对于复制初始化,候选函数是该类的所有转换构造函数(12.3.1)。

So, for copy-initialization, the copy constructor must be available. 因此,对于复制初始化,复制构造函数必须可用。 However, the compiler is permitted to 'optimize away' the copy: 但是,允许编译器“优化掉”副本:

12.2/1 Temporary objects 12.2 / 1临时物体

Even when the creation of the temporary object is avoided (12.8), all the semantic restrictions must be respected as if the temporary object was created. 即使在避免创建临时对象时(12.8),也必须遵守所有语义限制,就像创建临时对象一样。 [Example: even if the copy constructor is not called, all the semantic restrictions, such as accessibility (clause 11), shall be satisfied. [示例:即使未调用复制构造函数,也应满足所有语义限制,例如可访问性(第11节)。 ] ]

You can get the effect you want by avoiding copy-initialization and using direct-initialization: 您可以通过避免复制初始化和使用直接初始化来获得所需的效果:

 const A &b(B());  

Note: 注意:

Since newer versions of GCC apparently have a different behavior, I thought I'd post this note, which might address the difference (with both behaviors still standards conforming): 由于较新版本的GCC显然有不同的行为,我想我会发布这个注释,这可能会解决差异(两种行为仍符合标准):

8.5.3/5 References says: 8.5.3 / 5参考文献说:

If the initializer expression is an rvalue, with T2 a class type, and “cv1 T1” is reference-compatible with “cv2 T2,” the reference is bound in one of the following ways (the choice is implementation-defined): 如果初始化表达式是rvalue,T2是类类型,并且“cv1 T1”与“cv2 T2”引用兼容,则引用将以下列方式之一绑定(选择是实现定义的):

  • The reference is bound to the object represented by the rvalue (see 3.10) or to a sub-object within that object. 引用绑定到由rvalue(参见3.10)表示的对象或该对象内的子对象。

  • A temporary of type “cv1 T2” [sic] is created, and a constructor is called to copy the entire rvalue object into the temporary. 创建临时类型为“cv1 T2”[sic],并调用构造函数将整个右值对象复制到临时对象中。 The reference is bound to the temporary or to a sub-object within the temporary. 引用绑定到临时或临时内的子对象。

The constructor that would be used to make the copy shall be callable whether or not the copy is actually done. 无论副本是否实际完成,用于制作副本的构造函数都应该是可调用的。

I originally read the last sentence ("the constructor that would be used...") to apply to both options, but maybe it should be read as only applying to the seconds option - or at least maybe that's how the GCC maintainers are reading it. 我最初阅读最后一句(“将要使用的构造函数......”)来应用于这两个选项,但也许应该只读取应用于秒选项 - 或者至少可能是GCC维护者正在阅读的内容它。

I'm not sure if this is what's going on between the differing behavior of GCC versions (comments welcome). 我不确定GCC版本的不同行为之间是否存在这种情况(欢迎评论)。 We're definitely reaching the limits of my language-lawyering skills... 我们肯定达到了我的语言 - 律师技能的极限......

I think it is indeed a compiler bug, gcc seems to think that is is copy initialization. 我认为这确实是一个编译器bug,gcc似乎认为是复制初始化。 Use direct initialization instead: 使用直接初始化代替:

const A& b(B());

The copy-constructor call in copy initialization is always optimized away (an instance of copy elision), and then doesn't have to be available. 复制初始化中的复制构造函数调用总是被优化掉(复制省略的实例),然后不必可用。

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

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