С++ имеет весьма разнообразный синтаксис для конструирования объектов. Надо признать, что порой этот синтаксис весьма неочевиден, и многие вещи надо просто знать, нежели догадаться, как они работают. Например:
class T {...}; ... T t = T(1);
По очевидной логике вещей данный код должен при создании экземпляра класса T
вызвать конструктор по умолчанию (без аргументов), затем создать временный объект с помощью конструктора с одним аргументом и скопировать его в исходный объект перегруженным оператором копирования (или может конструктором копирования? ведь слева и справа объекты явно типа T
…).
К сожалению, тут невозможно просто догадаться по логике, тут надо знать, как это прописано в стандарте. Все эти “тонкости” конечно очевидны для профессионала, но у начинающих это порой вызывает непонимание, и как следствие использование однажды опробованных штампов “так работает” без какой-либо попытки что-то изменить.
Именно для таких случаев я обычно даю следующий пример, который покрывает часто используемые варианты создания объектов. Разобрав его один раз целиком, можно использовать его как подсказку в будущем, когда опять возникает вопрос “а что ж здесь будет вызвано: конструктор или оператор копирования?…“.
Итак, файл ctor.cpp
:
#include <iostream> class T { public: T() { std::cout << "T()" << std::endl; } T(int) { std::cout << "T(int)" << std::endl; } T(int, int) { std::cout << "T(int, int)" << std::endl; } T(const T&) { std::cout << "T(const T&)" << std::endl; } void operator=(const T&) { std::cout << "operator=(const T&)" << std::endl; } }; int main() { std::cout << "T t1 : "; T t1; std::cout << "T t2(1) : "; T t2(1); std::cout << "T t3 = 1 : "; T t3 = 1; std::cout << "T t4 = T(1) : "; T t4 = T(1); std::cout << "T t5(1, 2) : "; T t5(1, 2); std::cout << "T t6 = T(1, 2) : "; T t6 = T(1, 2); std::cout << "T t7; t7 = 1 : "; T t7; t7 = 1; std::cout << "T t8; t8 = T(1): "; T t8; t8 = T(1); std::cout << "T t9(t8) : "; T t9(t8); std::cout << "T t10 = 'a' : "; T t10 = 'a'; return 0; }
Компилируем, например в Visual Studio:
cl /EHsc ctor.cpp
и запускаем:
T t1 : T()
T t2(1) : T(int)
T t3 = 1 : T(int)
T t4 = T(1) : T(int)
T t5(1, 2) : T(int, int)
T t6 = T(1, 2) : T(int, int)
T t7; t7 = 1 : T()
T(int)
operator=(const T&)
T t8; t8 = T(1): T()
T(int)
operator=(const T&)
T t9(t8) : T(const T&)
T t10 = 'a' : T(int)
Видно, что во всех этих “разнообразных” способах создания объекта всегда вызывался непосредственно конструктор, а не оператор копирования. Оператор же копирования был вызван только когда знак присваивания использовался явно в отдельном от вызова конструктора операторе. То есть знак “=”, используемый в операторе конструирования объекта так или иначе приводит к вызову конструкторов, а не оператора копирования. И это происходит вне зависимости от какой-либо оптимизации, проводимой компилятором.
Также интересно, как был создана переменная t10
. Видно, что для символьной константы компилятор “подобрал” наиболее подходящий конструктор. Неявным образом был вызвал конструктор от int
. Если подобное поведение не входит в ваши планы, и вам совсем не нужно, чтобы конструктор от int
вызывался, когда идет попытка создать объект от типа, который может быть неявно преобразован в int
, например char
, то можно воспользоваться ключевым словом explicit
:
class T { public: ... explicit T(int) { std::cout << "T(int)" << std::endl; } ... };
Это запретит какое-либо неявное преобразования для аргумента этого конструктора.
Вообще практика объявления любого конструктора с одним параметром со модификатором explicit
является весьма полезной, и позволяет избежать некоторых неприятных сюрпризов, например, если вы хотели вызвать конструктор строки от типа char
, предполагая создать строку, состоящую только из одного символа, а получилось, что этот класс не имеет такого конструктора. Зато есть конструктор от int
, делающий совершенно не то, что вам нужно. Вот и будет сюрприз в виде символьной константы, истолкованной как целое число.
Я обычно по умолчанию пишу explicit
для конструкторов с одним параметром, и очень редко приходится потом убирать этого слово. Тут как со словом const
— сначала можно написать, а потом уже думать нужно ли тут его убрать или нет.