意外的 const 引用行为

发布于 2024-09-10 01:44:54 字数 1148 浏览 6 评论 0原文

#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 中删除“private”,我会得到我期望的输出:

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

我的问题是:为什么创建一个不称为 private 的方法会更改此代码是否可以编译?这是标准规定的吗?有解决方法吗?

#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:

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:

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?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

丿*梦醉红颜 2024-09-17 01:44:54

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

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

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

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

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

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

请注意,根据 CWG 缺陷 391。新语言如下(N3092 §8.5.3):

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

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

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

然后,在第一种情况下,引用将绑定到初始值设定项表达式右值,在第二种情况下,引用将绑定到作为转换结果的对象(或者在任一情况下,绑定到对象的相应基类子对象)。

适用第一种情况,并且引用“直接绑定”到初始值设定项表达式。

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).

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):

-- The reference is bound to the object represented by the rvalue (see 3.10) or to a sub-object within that object.

-- A temporary of type "cv1 T2" [sic] is created, and a constructor is called to copy the entire rvalue object into the temporary. 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. The new language reads (N3092 §8.5.3):

Otherwise, if T2 is a class type and

-- the initializer expression is an rvalue and "cv1 T1" is reference-compatible with "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)),

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).

The first case applies and the reference is "bound directly" to the initializer expression.

天煞孤星 2024-09-17 01:44:54

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

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);

,所选构造函数的重载为:

当类类型的对象是直接初始化 (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”类型的临时对象[原文如此],并调用构造函数将整个右值对象复制到临时对象中。该引用绑定到临时对象或临时对象中的子对象。

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

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

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

So what you're using is 'copy-initialization':

8.5/11 Initializers

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

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

T x(a);

In 13.3.1.3 "Initialization by constructor", the overloads for the constructor chosen are:

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. 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.

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

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. [Example: even if the copy constructor is not called, all the semantic restrictions, such as accessibility (clause 11), shall be satisfied. ]

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):

8.5.3/5 References says:

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):

  • The reference is bound to the object represented by the rvalue (see 3.10) or to a sub-object within
    that object.

  • A temporary of type “cv1 T2” [sic] is created, and a constructor is called to copy the entire rvalue object into the temporary. 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.

I'm not sure if this is what's going on between the differing behavior of GCC versions (comments welcome). We're definitely reaching the limits of my language-lawyering skills...

凑诗 2024-09-17 01:44:54

我认为这确实是一个编译器错误,gcc似乎认为这是复制初始化。请改用直接初始化:

const A& b(B());

复制初始化中的复制构造函数调用始终会被优化掉(复制省略的实例),因此不必可用。

I think it is indeed a compiler bug, gcc seems to think that is is copy initialization. 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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文