【问题标题】:Implementation supplied copy constructor and assignment operator实现提供了复制构造函数和赋值运算符
【发布时间】:2011-05-19 07:03:00
【问题描述】:

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

  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 

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

【问题讨论】:

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


    【解决方案1】:

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

    如果类 复制构造函数是 隐式定义有:

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

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

    如果类 复制赋值运算符 隐式定义有:

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

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

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

    【讨论】:

      【解决方案2】:

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

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

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

      【讨论】:

        【解决方案3】:

        你的情况(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。

        【讨论】:

          【解决方案4】:

          我认为两者之间的区别取决于您的具体实现的细节(并且没有太大区别)。对于它的价值,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,都无关紧要。

          【讨论】:

            【解决方案5】:

            这就是发生的事情:

            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=。

            【讨论】:

            • 这不是确切发生的事情。 struct B 的给定定义是非法的。但是原始的struct B 具有隐式operator= 本身是合法的,并且只有在尝试使用隐式operator= 时才会发生编译器错误。
            • @aschepler 请详细说明为什么上述内容是非法的?据我所见,代码中没有错,除了将 b2 分配给 b1 时出现编译错误。
            • 如果你把它复制到一个文件中,删除或注释掉main(),你仍然会在第11行A::operator=( other );得到一个编译器错误。此处使用A::operator= 的用户定义方法无论是否使用都是格式错误的。隐式声明的方法只有在使用时才格式错误。
            • @aschepler 啊哈好的。我只是演示了默认 operator= 的样子。
            【解决方案6】:

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

            §12.8/8:

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

            §12.8/12:

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

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

            所以合成的复制构造函数被声明并定义,但定义为删除。

            【讨论】:

            • 目前的草案不是标准,但我认为标准是相似的。它只是不使用“定义为已删除”的语言,而是说如果定义了隐式构造函数,则程序格式错误,而这反过来仅在使用时才会发生。
            猜你喜欢
            • 2011-06-09
            • 2012-06-27
            • 2011-07-19
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2013-04-13
            相关资源
            最近更新 更多