Я часто видел, что люди создают объекты на C ++, используя
Thing myThing("asdf");
Вместо этого:
Thing myThing = Thing("asdf");
Кажется, это работает (с использованием gcc), по крайней мере, до тех пор, пока не используются шаблоны. У меня вопрос: верна ли первая строка и стоит ли ее использовать?
Ответы:
Обе строки на самом деле правильные, но делают несколько разные вещи.
Первая строка создает новый объект в стеке, вызывая конструктор формата
Thing(const char*)
.Второй вариант немного сложнее. По сути, он делает следующее
Thing
с помощью конструктораThing(const char*)
Thing
с помощью конструктораThing(const Thing&)
~Thing()
объекта, созданного на шаге №1источник
Thing myThing = Thing(...)
не использует оператор присваивания, он по-прежнемуThing myThing(Thing(...))
создается копированием, как сказано , и не включает построение по умолчаниюThing
(редактировать: сообщение было впоследствии исправлено)Я предполагаю, что во второй строке вы действительно имеете в виду:
Thing *thing = new Thing("uiae");
что было бы стандартным способом создания новых динамических объектов (необходимых для динамического связывания и полиморфизма) и сохранения их адреса в указателе. Ваш код делает то, что описал JaredPar, а именно создает два объекта (один передал a
const char*
, другой передал aconst Thing&
), а затем вызывает деструктор (~Thing()
) для первого объекта (const char*
).Напротив, это:
Thing thing("uiae");
создает статический объект, который автоматически уничтожается при выходе из текущей области видимости.
источник
Компилятор может оптимизировать вторую форму до первой, но это не обязательно.
#include <iostream> class A { public: A() { std::cerr << "Empty constructor" << std::endl; } A(const A&) { std::cerr << "Copy constructor" << std::endl; } A(const char* str) { std::cerr << "char constructor: " << str << std::endl; } ~A() { std::cerr << "destructor" << std::endl; } }; void direct() { std::cerr << std::endl << "TEST: " << __FUNCTION__ << std::endl; A a(__FUNCTION__); static_cast<void>(a); // avoid warnings about unused variables } void assignment() { std::cerr << std::endl << "TEST: " << __FUNCTION__ << std::endl; A a = A(__FUNCTION__); static_cast<void>(a); // avoid warnings about unused variables } void prove_copy_constructor_is_called() { std::cerr << std::endl << "TEST: " << __FUNCTION__ << std::endl; A a(__FUNCTION__); A b = a; static_cast<void>(b); // avoid warnings about unused variables } int main() { direct(); assignment(); prove_copy_constructor_is_called(); return 0; }
Вывод из gcc 4.4:
TEST: direct char constructor: direct destructor TEST: assignment char constructor: assignment destructor TEST: prove_copy_constructor_is_called char constructor: prove_copy_constructor_is_called Copy constructor destructor destructor
источник
Проще говоря, обе строки создают объект в стеке, а не в куче, как это делает «новый». Вторая строка фактически включает второй вызов конструктора копирования, поэтому его следует избегать (это также необходимо исправить, как указано в комментариях). Вы должны как можно чаще использовать стек для небольших объектов, так как он быстрее, однако, если ваши объекты будут жить дольше, чем кадр стека, то это явно неправильный выбор.
источник
В идеале компилятор оптимизирует вторую, но это не обязательно. Первый - лучший способ. Однако очень важно понимать различие между стеком и кучей в C ++, так как вы должны управлять своей собственной памятью кучи.
источник
Я немного поигрался с ним, и синтаксис кажется довольно странным, когда конструктор не принимает аргументов. Приведу пример:
#include <iostream> using namespace std; class Thing { public: Thing(); }; Thing::Thing() { cout << "Hi" << endl; } int main() { //Thing myThing(); // Does not work Thing myThing; // Works }
так что просто написание Thing myThing без скобок фактически вызывает конструктор, в то время как Thing myThing () делает компилятор, который вы хотите создать, указатель на функцию или что-то в этом роде ?? !!
источник
В приложении к ответу JaredPar
1-обычный ctor, второй-подобный функции-ctor с временным объектом.
Скомпилируйте этот источник где-нибудь здесь http://melpon.org/wandbox/ с разными компиляторами
// turn off rvo for clang, gcc with '-fno-elide-constructors' #include <stdio.h> class Thing { public: Thing(const char*){puts(__FUNCTION__ );} Thing(const Thing&){puts(__FUNCTION__ );} ~Thing(){puts(__FUNCTION__);} }; int main(int /*argc*/, const char** /*argv*/) { Thing myThing = Thing("asdf"); }
И вы увидите результат.
Из ISO / IEC 14882 2003-10-15
Ваша 1-я, 2-я конструкция называется прямой инициализацией
Где почитать про РВО:
Отключите его с помощью флага компилятора из комментария, чтобы просмотреть такое поведение копирования)
источник