8
#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?

4

3 回答 3

4

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

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

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

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

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

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

请注意,根据CWG 缺陷 391的解决方案,这在 C++0x 中有所更改。新语言如下(N3092 §8.5.3):

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

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

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

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

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

于 2010-07-14T19:03:48.063 回答
3

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

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 版本的不同行为之间发生的情况(欢迎发表评论)。我们肯定达到了我的语言律师技能的极限......

于 2010-07-14T18:48:32.823 回答
1

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

const A& b(B());

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

于 2010-07-14T18:47:25.803 回答