实现提供了复制构造函数和赋值运算符

时间:2010-12-02 17:46:49

标签: c++ copy-constructor noncopyable copy-assignment

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

  1. 当我们在班级中声明复制ctor和/或复制赋值运算符时。
  2. 当我们从具有私人复制ctor和/或复制赋值运算符的类派生时,有些人说。
  3. 我对第二种情况有点困惑,第二种情况正是如此 a)实现不会为您声明它们,因此您将收到编译时错误 OR
    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;
    }
    

    Microsoft编译器输出

    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 
    

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

6 个答案:

答案 0 :(得分:7)

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

  

如果上课,程序就会生成错误   复制构造函数是   隐式定义具有:

     
      
  • 具有类的类型(或其数组)的非静态数据成员   无法访问或模糊的副本   构造函数,或
  •   
  • 具有无法访问或模糊副本的基类   构造即可。
  •   

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

  

如果上课,程序就会生成错误   复制赋值运算符   是隐式定义有:

     
      
  • const类型的非静态数据成员,或
  •   
  • 引用类型的非静态数据成员,或
  •   
  • 具有类的类型(或其数组)的非静态数据成员   无法访问的副本分配操作员,   或
  •   
  • 具有无法访问的复制赋值运算符的基类。
  •   

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

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

答案 1 :(得分:3)

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

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

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

答案 2 :(得分: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&)是更好的重载,但隐式定义是错误的。如果没有该声明,您可能认为编译器应隐式地将b2转换为int,然后将其分配给b1

答案 3 :(得分: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来检测它,都无关紧要。

答案 4 :(得分: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;
}

默认运算符=尝试调用A :: operator =,这是私有的。

答案 5 :(得分:0)

标准似乎与你同意。引用目前的草案:

§12.8/ 8:

  

如果类定义没有   显式声明一个复制构造函数   并且没有用户声明的移动   构造函数,复制构造函数是   隐含地宣称为违约   (8.4)。

§12.8/ 12:

  

默认的复制/移动构造函数   类X被定义为已删除   (8.4.3)如果X有:[...]

     
      
  • 无法复制/移动的直接或虚拟基类B,因为   重载决议(13.3),如适用   到B的相应构造函数,   导致歧义或功能   被删除或无法访问的   默认构造函数[...]
  •   

因此声明并定义了合成的复制构造函数,但定义为已删除。