2

对于实现(编译器)不提供复制构造函数和复制赋值运算符的情况,我有一点困惑。

  1. 当我们在类中声明复制 ctor 和/或复制赋值运算符时。
  2. 有人说,当我们从具有私有复制 ctor 和/或复制赋值运算符的类派生时。

我对第二种情况有点迷茫,正是第二种情况。
a) 实现不会为您声明它们,因此您将收到编译时错误。
或者
b) 实现将声明和定义它们,但是当编译器定义的实现试图找到基类的方法时,我们会得到一个编译时错误。

我昨天接受了采访,我说它的(b)正在发生,但面试官不同意,他说它的(a)。

我尝试在 Microsoft C/C++ 14.00 和 gcc 4.4.5 中编译以下代码

struct A
{
private:
  A& operator = ( const A& );
};

struct B : A
{
};


int main()
{
  B b1;
  B b2;
  b1 = b2;

  return 0;
}

微软编译器输出

ctor01.cpp(9) : error C2248: 'A::operator =' : cannot access private member declared in class 'A'
ctor01.cpp(4) : see declaration of 'A::operator ='
ctor01.cpp(2) : see declaration of 'A'
This diagnostic occurred in the compiler generated function 'B &B::operator =(const B &)'

gcc 编译器输出

Ctor01.cpp: In member function ‘B& B::operator=(const B&)’:
Ctor01.cpp:4: error: ‘A& A::operator=(const A&)’ is private
Ctor01.cpp:8: error: within this context
Ctor01.cpp: In function ‘int main()’:
Ctor01.cpp:15: note: synthesized method ‘B& B::operator=(const B&)’ first required here 

所以我认为,实现会声明并定义它,但是当编译器定义的实现试图找到基类方法时,我们会得到一个编译时错误。如果我错了,请纠正我。

4

6 回答 6

7

关于复制构造函数,这就是标准所说的 (12.8/7) :

如果 隐式定义了复制构造函数的类具有以下特征,则程序是错误的:

  • 具有不可访问或不明确的复制构造函数的类类型(或其数组)的非静态数据成员,或
  • 具有不可访问或模棱两可的复制构造函数的基类

关于复制赋值运算符(12.8/12):

如果隐式定义了复制赋值运算符的类具有以下特征,则程序是错误的:

  • const 类型的非静态数据成员,或
  • 引用类型的非静态数据成员,或
  • 具有不可访问的复制赋值运算符的类类型(或其数组)的非静态数据成员,或
  • 具有不可访问的复制赋值运算符的基类。

从我的角度来看,编译器如何报告错误,或者它实际上是如何陷入其中的,几乎无关紧要。

但是,我确实相信答案 (b) 可能正确:声明了基类副本分配,并且无法访问。派生类有一个隐式声明的复制赋值,如果使用,编译器将尝试定义 它,从而使程序格式错误。

于 2010-12-02T18:03:23.147 回答
3

如果没有用户声明的版本,则一个类将隐式声明一个复制构造函数和一个复制赋值运算符。这总是发生。

简单地说,只有在实际使用它们时,实现才会隐式定义它们。如果,当实现尝试定义它们时,隐式定义格式错误(例如,对于复制赋值,类包含引用成员或 const 成员,或者对于复制构造函数,基类或成员具有私有复制构造函数),则程序格式不正确。

如果程序包含隐式声明的复制构造函数和复制赋值运算符的类,只要它不会通过使用它们或导致它们被使用来实际定义它们,程序仍然可以有效。

于 2010-12-02T18:06:20.133 回答
2

你的情况(b)更准确。

C++03 标准 12.8p10

如果类定义没有显式声明复制赋值运算符,则式声明一个。

和 12.8p12

当为其类类型的对象分配其类类型的值或从其类类型派生的类类型的值时,隐式声明的复制赋值运算符被隐式定义。如果隐式定义了复制赋值运算符的类具有以下条件,则程序是非良构的:

  • 类型的非静态数据成员const,或
  • 引用类型的非静态数据成员,或
  • 具有不可访问的复制赋值运算符的类类型(或其数组)的非静态数据成员,或
  • 具有不可访问的复制赋值运算符的基类。

隐式定义的复制构造函数、默认构造函数和析构函数的相应要求有类似的措辞。

指定方法存在,即使它们的定义是非法的,也澄清了一些关于重载决议的事情。例如,

class A {
private:
  A& operator=(const A&);
};

class B : public A {
public:
  operator int() const;
  B& operator=(int);
};

void f(B& b1, const B& b2)
{ b1 = b2; }

是非法的,因为隐式声明B::operator=(const B&)是更好的重载,但隐式定义格式不正确。如果没有该声明,您可能会认为编译器应该隐式转换b2int,然后将其分配给b1.

于 2010-12-02T18:36:45.623 回答
1

我认为两者之间的区别取决于您的具体实现的细节(并且没有太大区别)。对于它的价值,Comeau 给出了这个:

"ComeauTest.c", line 7: error: "A &A::operator=(const A &)" (declared at line 4) is
          inaccessible
  struct B : A
             ^
          detected during implicit generation of "B &B::operator=(const B &)"
                    at line 16

1 error detected in the compilation of "ComeauTest.c".

因此,在该编译器上,它会在 B 的赋值运算符的隐式生成“期间”检测到错误。换句话说,它试图生成它,但发现它不能。无论是在写出它时检测到它,还是通过A直接查看,都无关紧要。

于 2010-12-02T18:03:07.840 回答
0

这就是发生的事情:

struct A
{
private:
  A& operator = ( const A& );
};

struct B : A
{
  B& operator = ( const B& other )
  {
    A::operator=( other );
    return *this;
  }
};


int main()
{
  B b1;
  B b2;
  b1 = b2;

  return 0;
}

默认 operator= 尝试调用私有的 A::operator=。

于 2010-12-02T17:59:53.907 回答
0

该标准似乎与您一致。引用当前草案:

§12.8/8:

如果类定义没有显式声明复制构造函数并且没有用户声明的移动构造函数,则复制构造函数被隐式声明为默认值 (8.4)。

§12.8/12:

如果 X 具有以下属性,则类 X 的默认复制/移动构造函数被定义为已删除 (8.4.3):[…]

  • 无法复制/移动的直接或虚拟基类 B,因为应用于 B 的相应构造函数的重载解析 (13.3) 会导致歧义或从默认构造函数中删除或无法访问的函数 […]

所以合成的拷贝构造函数被声明和定义,但被定义为删除。

于 2010-12-02T18:04:12.713 回答