#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
我的问题是:为什么制作一个不称为私有的方法是否会改变此代码?这是标准规定的吗?有解决方法吗?
答案 0 :(得分:4)
当前标准(C ++ 03)中的重要措辞似乎在§8.5.3中,它解释了如何初始化引用(在这些引号中,T1
是被初始化的引用的类型, T2
是初始化表达式的类型。)
如果初始化表达式是右值,
T2
类类型,而“cv1 T1
”与“cv2 T2
”引用兼容,则引用绑定在其中一个以下方式(选择是实现定义的):- 引用绑定到rvalue(参见3.10)表示的对象或该对象中的子对象。
- 创建类型为“
cv1 T2
”[sic]的临时表,并调用构造函数将整个右值对象复制到临时对象中。引用绑定到临时或临时的子对象。无论副本是否真正完成,用于制作副本的构造函数都应该是可调用的。
因此,即使实现将引用直接绑定到临时对象,也必须可以访问复制构造函数。
请注意,根据CWG defect 391的分辨率,这在C ++ 0x中已更改。新语言为(N3092§8.5.3):
否则,如果
T2
是类类型且- 初始值设定项表达式为右值,“
cv1 T1
”与“cv2 T2
”引用兼容,-
T1
与T2
不是引用相关的,初始化表达式可以隐式转换为“cv3 T3"
类型的右值(通过枚举适用的转化来选择此转换)函数(13.3.1.6)并通过重载决策(13.3)选择最佳函数,然后引用绑定到第一种情况下的初始化表达式rvalue和第二种情况下转换结果的对象(或者,在任何一种情况下,绑定到对象的相应基类子对象)。
第一种情况适用,引用“直接绑定”到初始化表达式。
答案 1 :(得分:3)
所以你使用的是'复制初始化':
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版本的不同行为之间是否存在这种情况(欢迎评论)。我们肯定达到了我的语言律师技能的极限......
答案 2 :(得分:1)
const A& b(B());
复制初始化中的复制构造函数调用总是被优化掉(复制省略的实例),然后不一定可用。