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

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

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; тоже? Это похоже на сгенерированный конструктор копий, но не генерирует оператор присваивания. Разве эти два не связаны друг с другом? имеет ли смысл генерировать реализацию по умолчанию только для одного из них, когда другой не может быть сгенерирован?

Ответ 1

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

Вопрос 1: Почему это предупреждение?
Ссылки могут быть инициализированы только после их создания. Вы не можете переназначить ссылку на другую переменную того же типа после создания, потому что Reference является просто псевдонимом переменной типа, для которой она была создана, и будет продолжать оставаться таковой. Попытка переназначить это порождает ошибку.
Обычно компилятор генерирует неявный бит оператор присваивания каждому классу по умолчанию, но в этом случае Поскольку 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.

Отметьте те же здесь.

Ответ 2

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

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

Однако сами ссылки не могут быть назначены (переназначены), поэтому компилятор отказывается генерировать неявные операторы присваивания копий для классов со ссылками. Компилятор сообщил вам об этом, выпустив предупреждение. Если вы действительно попытаетесь использовать оператор присваивания для класса B в своей программе, вы получите ошибку.

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

Ответ 3

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

Ответ 4

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

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

Ответ 5

B b(a); - действительный оператор. Поскольку B::B(A&) вызывается, когда вы передаете объект типа A конструктору B.

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