понедельник, сентября 07, 2009

Конструктор или оператор присваивания?

В C++ не всегда бывает так, что знак = означает вызов оператора присваивания, из-за чего народ начинает путаться.

Я нашла в comp.lang.c++.moderated большой хороший пример, может пригодится кому.


class B { ... };

class A
{
...
public:
// Constructor
A() { ... }

// Copy constructor
A(const A& a_obj) { ... }

// Constructor overloading
A(const B& b_obj) { ... }
...
A& operator=(const A& a_src) { ... }
...

};

// Примеры:
A a1; // конструктор вида A()
A a2(a1); // конструктор копирования A(a1)
A a3 = a2; // конструктор копирования A(a2)

B b;
A a4(b); // перегруженный конструктор A(b)
A a5 = b; // перегруженный конструктор A(b)

a1 = a5; // operator=(const A&) то есть operator=(a5)
a2 = b; // Шаг1: создается временный объект класса а A
// с помощью конструктора вида A(b),
// Шаг2: -> operator=(const A&) то есть operator=(A(b))

Если будете экспериментировать, не забудьте выключить оптимизацию.

18 коммент.:

rikkitikkitavi комментирует...

Я бы еще добавил про последнюю строчку:
там - как и описано Алёной - происходят не очевидные с первого взгляда вещи, что есть не очень хорошо,
в связи с этим, например, Гугл рекомендует конструкторы с одним параметром объявлять как explicit, чтобы такие вещи явно прописывались в коде и не прятали за собой сюрпризов - ведь можно подумать, что скорее всего тут сработатывает оператор копирования

fuwaneko комментирует...

Ну, товарищи знакомые с языками вроде Erlang знают, что оператор присваивания не то, чем кажется :) Точнее, что его вообще может не быть.

Alex комментирует...

Не знаю как кому но меня это в заблуждение никогда не вводило:) ИХМО это такие вещи в которых путатцо нильзя.

wrathchildtoo комментирует...

К слову, конструктор такого типа
A(const B& b_obj) { ... }
называется конструктором преобразования

Анонимный комментирует...

Не совсем корректный пример!!!

// да. конструктор A()
A a1;

// это - вызов explicit
// конструктора A(const A &),
// который есть конструктор
// копирования
// *по совместительству*
A a2(a1);

А то что rikkitikkitavi сказал насчет explicit- так любая декларация объекта- это вызов конструктора либо со знаком "=" либо explicit- со скобочками, либо без всего.

В моем понимании- существует "нативный" контекст конструктора копирования- это вызов, который генерируется компилятором для передачи значений по стеку. Но в контексте декларации- это обычный конструктор.

Roman комментирует...

А как происходит у вас на практике?
То есть стараетесь вы объявлять конструкторы копирования explicit?
Или же за этим четко не следите?

rikkitikkitavi комментирует...

2 Roman: честно говоря, Google C++ Style Guide я прочитал совсем недавно, с тех пор стараюсь так писать, а до этого сам как-то не думал

с гуглом я согласен - когда вы работаете совместно в большой команде, когда вы работаете с сотнями классов, большей частью вообще не вами написанных, то начинаешь сильно ценить ясность и читабельность кода

rikkitikkitavi комментирует...

все мои слова относятся в первую очередь к конструкторам "преобразования"

не глядя в объявления классов вы можете сказать сколько объектов класса А будет создано

B b;
A a;
a = b;

?
правильно, не можете
глядя на этот код большинство людей ожидает что выполнится оператор копирования
а теперь представьте, что по каким-то причинам создание временного объекта критично и/или бажно
сколько времени вы потратите на поиск этого

примерно такие доводы у гугла, почитайте

stalkerg комментирует...

В целом очевидные вещи но, если копаешь чужой код то это просто ад. (Вспомниаю кучу таких классов в Вангерах)

Анонимный комментирует...

2 rikkitikkitavi

Большое спасибо за наводку на Google C++ Style Guide :)

Саша комментирует...

Если я ничего не путаю, поведение компилятора в случае со строчкой
A a2 = a1;
зависит от "настроения" компилятора и может интерпретироваться либо как вызов конструктора копирования, либо как вызов конструктора по умолчанию и последующего вызова оператора присваивания.

rikkitikkitavi комментирует...

согласно стандарту, эта строчка должна интерпретироваться компилятором как вызов конструктора копирования

Kodt комментирует...

Чтобы избежать сюрпризов, нужно все конструкторы, операторы приведения типов и операторы присваивания делать согласованными.
Тогда нет особой разницы (кроме просадки производительности), что за цепочка там сработала, да ещё с учётом RVO всяческого.

Либо бить по рукам с помощью explicit или private.

DAloG комментирует...

Присоединяюсь к благодарностям за Google C++ code style))

Дмитрий комментирует...

По-моему, сложнее всего для новичков отличить случай вызова конструктора с аргументом от случая с последовательным вызовом конструктора с аргументом в правой части присваивания и, собственно, оператора присваивания.
P.S. Алене спасибо за пост и за помощь по этому вопросу :) Также присоединяюсь к благодарностям за Google C++ Style Guide.

DaddyM комментирует...

Scott Meyers вообще сказал: если вы не видите, почему Ваш конструктор должен быть не explicit, делайте его всегда explicit.
Думаю он прав.

Сергей Зеленовский комментирует...

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

2. STL при копировании объектов (в процессе сортировки, например) используют конструктор копирования или оператор присваивания? А может и то и другое... Как контролировать этот процесс (чтобы STL делал "легкое копивание").

Alena комментирует...

Сергей Зеленовский
Пара связанных с темой (и между собой) вопросов.
1. При копировании сложного объекта (конструктор которого создает внутренние объекты) можно сделать легкое копирование (копируя только внутренние ссылки), а можно - "тяжелое", создав внутренние объекты заново. Есть стандарты - какой вариант предпочтителен для конструктора копирования, а какой для оператора присваивания? Или оба варианта активно используются и там и там и пока не посмотришь - не узнаешь?


Это часто обсуждаемый вопрос, deep copy vs. shallow copy.

Разработчик решает нужны ли ему лишь ссылки или надо клонировать объект.

2. STL при копировании объектов (в процессе сортировки, например) используют конструктор копирования или оператор присваивания? А может и то и другое... Как контролировать этот процесс (чтобы STL делал "легкое копивание").

STL может быть имплементирован по-разному. В C++11 для этого вообще используется move semantics.
Конструктор копирования и оператор присваивания должны быть корректно определены для данного класса. Полагаться на детали реализации STL, которые к тому же еще могут меняться, я бы не стала.