Реализация предоставленного конструктора копии и оператора присваивания

У меня небольшая путаница в отношении ситуаций, когда реализация (компилятор) не будет предоставлять конструктор копирования и оператор присваивания копии.

  1. Когда мы объявляем копию ctor и / или оператор присваивания копирования в нашем классе.
  2. Кто-то говорит, когда мы наследуем класс, у которого есть личный экземпляр копии и / или оператор присваивания копии.

Меня немного смущает вторая ситуация, это вторая ситуация точно.
а) Реализация не объявит их для вас, поэтому вы получите ошибку во время компиляции.
ИЛИ ЖЕ
b) Реализация объявит и определит их, но когда реализация, определенная компилятором, попытается найти метод базового класса, мы получим ошибку времени компиляции.

У меня вчера было интервью, я сказал, что его (б) происходит, но интервьюер не согласен, он говорит, что это (а).

Я попытался скомпилировать следующий код в 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 ответов

Решение

Относительно конструктора копирования, это то, что стандарт говорит (12.8/7):

Программа плохо информирована, если класс, для которого неявно определен конструктор копирования, имеет:

  • нестатический член данных типа класса (или его массив) с недоступным или неоднозначным конструктором копирования, или
  • базовый класс с недоступным или неоднозначным конструктором копирования.

Относительно оператора присвоения копии (12.8/12):

Программа плохо информирована, если класс, для которого неявно определен оператор присваивания копии, имеет:

  • нестатический член данных типа const, или
  • нестатический элемент данных ссылочного типа, или
  • нестатический член данных типа класса (или его массив) с недоступным оператором назначения копирования, или
  • базовый класс с недоступным оператором присваивания копии.

То, как компилятор сообщает об ошибке, или как она на самом деле попадает в нее, с моей точки зрения не очень важно.

Тем не менее, я верю, что ответ (b), вероятно, является более правильным: назначение копии базового класса объявлено, и оно недоступно. Производный класс имеет неявно объявленное присвоение копии, которое компилятор попытается определить, если оно используется, что делает программу плохо сформированной.

Класс будет иметь конструктор копирования и оператор присваивания копии, неявно объявленный, если не существует ни одной объявленной пользователем версии. Это всегда случается.

Упрощенно, реализация будет неявно определять их, только если они фактически используются. Если, когда реализация пытается определить их, неявное определение будет некорректно сформировано (например, для назначения копирования класс содержит ссылочный член или член const или для конструктора копирования у базы или члена есть частный конструктор копирования), тогда программа плохо сформирован.

Программа по-прежнему может быть действительной, если она содержит классы, которые неявно объявляют конструкторы копирования и операторы присваивания копирования, которые не могут быть неявно определены, если только они не приводят к их фактическому определению с помощью их использования или к их использованию.

Ваш случай (б) является более точным.

C++ 03 Standard 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,

Я думаю, что различие между ними зависит от деталей вашей конкретной реализации (и не имеет большого значения). Для чего это стоит, Комо дает это:

"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 напрямую, на самом деле не имеет значения.

Вот что происходит:

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=, который является закрытым.

Стандарт вроде бы с тобой согласен. Цитирование из текущего проекта:

§12.8 / 8:

Если определение класса не объявляет явно конструктор копирования, и не существует объявленного пользователем конструктора перемещения, конструктор копирования неявно объявляется как дефолтный (8.4).

§12.8 / 12:

По умолчанию конструктор копирования / перемещения для класса X определяется как удаленный (8.4.3), если X имеет: […]

  • прямой или виртуальный базовый класс B, который нельзя скопировать / переместить, поскольку разрешение перегрузки (13.3) применительно к соответствующему конструктору B приводит к неоднозначности или функции, которая удаляется или недоступна из конструктора по умолчанию […]

Таким образом, конструктор синтезированной копии объявляется и определяется, но определяется как удаленный.

Другие вопросы по тегам