Оператор присваивания и конструктор копирования при наличии ссылок


Я просто экспериментирую со ссылками, используя этот код:

class A
{
};

class B
{
public:
    B(A& a): m_a(a){}

    A& m_a;
};

int main()
{
    A a;
    B b(a);
    B b1 = b;
}

Я ожидал, что оба B b1 = b; приведут к ошибке. Вместо этого, когда я компилирую с VS2008, я просто получаю предупреждение

Предупреждение C4512:' B': назначение оператор не может быть сгенерирован

Я понимаю, почему получаю это предупреждение. Но не должен ли компилятор генерировать ошибку и для оператора B b1 = b;? Это похоже на то, как он сгенерировал конструктор копирования, но не сгенерировал оператор присваивания. Разве они не связаны друг с другом по своей сути ? имеет ли смысл создавать реализацию по умолчанию только для одного из них, когда другой не может быть создан?
5 10

5 ответов:

warning C4512: 'B' : assignment operator could not be generated

Вопрос 1: Почему это предупреждение?
ссылки может быть инициализирован только один раз при их создании. Вы не можете переназначить ссылку на другую переменную того же типа после создания, потому что ссылка является просто псевдонимом переменной типа, для которой она была создана и будет оставаться таковой. Попытка переназначить его приводит к ошибке.
Обычно компилятор по умолчанию генерирует неявный битовый оператор присваивания для каждого класса но в этом случае, поскольку class B имеет ссылку в качестве члена m_a, если компилятор должен был бы генерировать неявный оператор присваивания, это нарушило бы фундаментальное правило, что ссылки не могут быть переназначены. Таким образом, компилятор генерирует это предупреждение, чтобы сообщить вам, что он не смог создать неявный оператор присваивания.

Вопрос 2: но разве компилятор не должен генерировать ошибку и для оператора B b1 = b;?
Сгенерированное предупреждение и это конкретные операции не имеют никакого отношения вообще.
B b1 = b; вызывает неявный (как справедливо указал @AndreyT) конструктор копирования B::B(const B&). Неявный конструктор копирования - это одна из функций-членов, которую класс создает по умолчанию. Так что нет никакого предупреждения или ошибки для него.

Вопрос 3: это похоже на то, что он сгенерировал конструктор копирования, но не сгенерировал оператор присваивания. Разве они не связаны друг с другом по своей сути ?
Нет, они вовсе не родственники. Да компилятор создал конструктор копирования, но не смог создать оператор присваивания по причине, указанной в ответе на вопрос 1 выше. Это происходит потому, что ссылка на элемент m_a может быть инициализирована в теле самого конструктора. это просто начальное назначение в момент создания, а не назначение, как в случае =.

Вопрос 4: имеет ли смысл генерировать реализацию по умолчанию только для одного из них, когда другой не может быть сгенерировано?
Ответ на вопрос 3, кажется, отвечает на это.

просто чтобы повторить операции, выполняемые в вашем примере кода:

B b(a); вызывает конструктор копирования преобразованияB::B(A&)
B b1 = b; вызывает конструктор копирования по умолчанию B::B(const B&)

Рассмотрим дополнительные сценарии.
Если у вас есть B b1 = a;, он вызовет B::B(A&) и, следовательно, снова не ошибется.

Но компилятор отметит ошибку, если B::B(A&) было объявлено explicit и не будет допущен ни для какого implicit conversions, действуя как conversion function.

Проверьте то же самое здесь.

Конструкторы в языке C++ выполняютинициализацию , в то время как операторы присваивания выполняютприсваивание . Инициализация и назначение - это совершенно разные понятия.

Ссылки в языке C++ могут быть инициализированы, поэтому компилятор не имеет проблем с созданием неявных конструкторов копирования для классов со ссылками. Оператор B b1 = b; использует неявно созданный конструктор копирования. Я не понимаю, почему вы ожидаете, что он произведет ошибка. Однако сами ссылки не могут быть назначены (переназначены), поэтому компилятор отказывается генерировать неявные операторы присваивания копий для классов со ссылками. Компилятор сообщил вам об этом, выдав предупреждение. Если вы действительно попытаетесь использовать оператор присваивания для класса B в своей программе, вы получите ошибку.

В этом отношении ситуация со ссылками в значительной степени аналогична ситуации с членами const: если некоторый класс имеет const члены, компилятор не будет иметь никаких проблем с созданием неявного конструктора копирования для этого класса,но откажется генерировать неявное назначение.

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

Вашему образцу не требуется оператор присваивания. VC++ просто предупреждает вас, что он не сможет создать его, если потребуется. Это может быть полезной информацией, если вы пишете библиотеку и забыли предвидеть, что пользователю вашей библиотеки может потребоваться скопировать B.

Если предупреждение вам не нужно, вы можете подавить его (например, с помощью # pragma), или вы можете объявить частный оператор присваивания и не реализовывать его. Если кто-то затем попытается вызвать назначение оператор, он выйдет из строя с ошибкой времени связи.

B b(a); является допустимым утверждением. Потому что B::B(A&) вызывается при передаче объекта типа A конструктору B.

Кстати, с g++ не генерируется предупреждение, как вы упомянули. (И ИМХО, там не должно быть никакого предупреждения, потому что B b1= b; вызвал конструктор копирования по умолчанию B::B(const B&).)