私有复制构造函数/赋值运算符和复制初始化
private copy constructor/assignment operator and copy-initialization
这是这个问题的后续内容
在下面的代码中,为什么第1行编译,而第2行和第3行不编译(使用visual C++2010)
class ABase
{
protected:
ABase() {}
~ABase() {}
private:
ABase( const ABase& );
const ABase& operator=( const ABase& );
};
class A : ABase
{
};
class B
{
public:
B() {}
~B() {}
private:
B( const B& );
const B& operator=( const B& );
};
int main( void )
{
A a = A(); // line 1
A a2( a ); // line 2
B b = B(); // line 3
return 0;
}
(注意BA是boost::non-copyable的副本)
编辑:我的问题不是知道为什么第2行和第3行不编译(我知道,复制构造函数是私有的),而是知道为什么第1行编译。
实际上,第1行不应该编译。
显然,在这种情况下,vc++2010在执行语言规则时存在问题(可能是因为它们与基类有关,而与对象本身无关)。
g++关于第1行的诊断消息是非常清晰的
ncopy.cpp: In copy constructor ‘A::A(const A&)’:
ncopy.cpp:7: error: ‘ABase::ABase(const ABase&)’ is private
ncopy.cpp:12: error: within this context
ncopy.cpp: In function ‘int main()’:
ncopy.cpp:27: note: synthesized method ‘A::A(const A&)’ first required here
编译器接受第一次使用是错误的。即使消除了副本,副本构造函数也必须是可访问的,代码才能正确。
在这种特殊情况下,A
:中有一个隐式声明的复制构造函数
§12.8/4如果类定义没有显式声明复制构造函数,则隐式声明一个。
这是隐含定义的:
§12.8/7如果隐式声明的复制构造函数用于从其类类型的对象或从其类类别派生的类类型的副本初始化其类类别的对象,则该构造函数被隐式定义108)。[注意:复制构造函数是隐式定义的,即使实现忽略了它的使用(12.2)。]如果为其隐式定义复制构造函数的类具有:,则程序是格式错误的
--具有不可访问或不明确的复制构造函数()的类类型(或其数组)的非静态数据成员
--具有不可访问的或不明确的复制构造函数的基类。
ABase( const ABase& );
复制构造函数是私有的,因此无法使用此私有复制构造函数创建类对象的副本,从而导致错误。
A a = A(); // line 1
使用A::A(const A&)
创建一个新的A
对象。A
源自ABase
,它调用ABase::ABase(const ABase&)
在其构造函数中是私有的,它也不会编译。
这是Ideone上的输出。它甚至不在gcc上编译。
为什么它适用于Visual studio
原因是visual C++编译器可能对返回值进行了优化,从而省略了复制构造函数。
根据C++标准,12.8复制类对象第15节
当满足某些条件时,允许实现省略类对象的复制构造,即使该对象的复制构造函数和/或析构函数有副作用。在这种情况下,实现将省略的复制操作的源和目标视为引用同一对象的两种不同方式,并且该对象的销毁发生在两个对象在没有优化的情况下被销毁的较晚时间。111)
请参阅我的答案此处,其中引用了标准和这方面的示例代码。
A a2( a ); // line 2
不编译的原因与ABase::ABase(const ABase&)
是私有的原因相同。
B b = B(); // line 3
不编译,因为B( const B& );
是私有的。
第1行是一个返回值优化(编译器认为无需为A()
创建临时变量,并使用复制构造函数/赋值运算符为变量a
赋值)。但是,这并不是在GCC(4.2.1版)上编译的,应该避免。
第2行没有编译,因为在这种情况下编译器没有为您生成赋值运算符。如您所料,第3行没有编译。
里根总结:第1行之所以有效,是因为它是微软,其他人的行为正如预期。
为什么要编译第1行?因为你的编译器坏了;不应该,根据标准。而你的类A
有一个隐含的声明的复制构造函数,标准§12.8/7规定如果是,则隐式声明的复制构造函数将被隐式定义用于初始化对象(如A a = A();
),并且程序如果构造函数隐式定义并且基类具有不可访问或不明确的复制构造函数。甚至有一张纸条说,即使该实现省略了复制构造函数。你的编译器不是走得够远了:它看到了隐式声明的公共副本构造函数,但它不会尝试隐式定义它,即使标准明确表示应该这样做。
我相信这是因为构造函数是受保护的,而不是私有的。类A中编译器提供的构造函数可以自由调用类ABase的受保护构造函数,因此它可以工作。
此外,第1行不是复制构造函数。带有赋值的声明是一种特殊情况,它被转换为构造函数。
- 不能将复制初始化与隐式转换的多个步骤一起使用
- 在引用初始化中使用已删除的复制构造函数进行复制初始化
- 为什么 std::string s = "123" 当不涉及副本时被视为复制初始化?
- 复制初始化:为什么即使关闭了复制省略,也没有调用move或copy构造函数
- 复制初始化与直接初始化已更改
- 为什么在直接初始化和赋值中传递 lambda 而不是在复制初始化中传递 lambda 时会编译?
- 复制初始化 - 从 'int' 类型转换为非标量类型
- 我可以制作一个对象方法,如果单独调用,它将自行修改,但如果在复制初始化期间调用,则会返回一个新对象?
- 如果值来自成员变量,则复制初始化和参考初始化之间的C 差异
- 复制初始化表单 '= {}'
- 对C++所做的更改使复制初始化适用于具有显式构造函数的类
- 显式强制转换、直接初始化和复制初始化之间的行为不同
- 是直接初始化还是复制初始化
- 为什么复制初始化是这样的?为什么需要复制构造函数
- 在复制初始化中,对复制构造函数的调用是显式的还是隐式的
- 复制列表初始化和传统复制初始化之间的任何区别
- 复制初始化和显式构造函数-编译器的差异
- 如何为显式重载构造函数启用复制初始化
- POD变量的直接初始化不起作用,但当将变量推到向量上时,复制初始化起作用
- 为什么类构造函数在通过复制初始化对象时不起作用