私有复制构造函数/赋值运算符和复制初始化

private copy constructor/assignment operator and copy-initialization

本文关键字:复制 初始化 赋值运算符 构造函数      更新时间:2023-10-16

这是这个问题的后续内容

在下面的代码中,为什么第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行不是复制构造函数。带有赋值的声明是一种特殊情况,它被转换为构造函数。

相关文章: