Вызов конструкторов в С++ без новых
Я часто видел, что люди создают объекты на С++, используя
Thing myThing("asdf");
Вместо этого:
Thing myThing = Thing("asdf");
Кажется, что это работает (с использованием gcc), по крайней мере, пока не задействованы шаблоны. Мой вопрос сейчас, первая строка правильная, и если так, я должен ее использовать?
Ответы
Ответ 1
Обе линии на самом деле правильны, но делают тонко разные вещи.
Первая строка создает новый объект в стеке, вызывая конструктор формата Thing(const char*)
.
Второй - немного сложнее. Это, по существу, делает следующее
- Создайте объект типа
Thing
с помощью конструктора Thing(const char*)
- Создайте объект типа
Thing
с помощью конструктора Thing(const Thing&)
- Вызов
~Thing()
объекта, созданного на шаге # 1
Ответ 2
Я предполагаю, что со второй строкой вы фактически подразумеваете:
Thing *thing = new Thing("uiae");
который стал бы стандартным способом создания новых динамических объектов (необходимых для динамической привязки и полиморфизма) и сохранения их адреса указателю. Ваш код делает то, что описал JaredPar, а именно создание двух объектов (один передал const char*
, другой передал const Thing&
), а затем вызвал деструктор (~Thing()
) для первого объекта (const char*
один).
В отличие от этого:
Thing thing("uiae");
создает статический объект, который автоматически уничтожается при выходе из текущей области.
Ответ 3
Компилятор может хорошо оптимизировать вторую форму в первой форме, но ее не нужно.
#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
Ответ 4
Проще говоря, обе строки создают объект в стеке, а не в куче, как это делает "новый". Вторая строка фактически включает второй вызов конструктора копирования, поэтому его следует избегать (его также нужно исправить, как указано в комментариях). Вы должны использовать стек для небольших объектов как можно больше, так как это быстрее, однако, если ваши объекты выживут дольше, чем кадр стека, то это явно неправильный выбор.
Ответ 5
В идеале, компилятор будет оптимизировать второй, но это не требуется. Первый - лучший способ. Тем не менее, очень важно понять разницу между стеком и кучей в С++, sine вы должны управлять своей собственной кучевой памятью.
Ответ 6
В приложении JaredPar ответьте
1-обычный ctor, 2nd-function-like-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
8.5, часть 12
Ваша первая, вторая конструкция называется прямой инициализацией
12.1, часть 13
Функциональное преобразование типа обозначения (5.2.3) может быть использовано для создания новые объекты его типа. [Примечание: синтаксис выглядит как явный вызов конструктора. ]... Объект, созданный таким образом, не указан. [Примечание: 12.2 описывает время жизни временных объектов. ] [Заметка: Явные вызовы конструктора не дают lvalues, см. 3.10. ]
Где читать о RVO:
12 Специальные функции-члены/12.8 Копирование объектов класса/Часть 15
Когда выполняются определенные критерии, реализация может пропустить построение копии объекта класса, даже, если конструктор копирования и/или деструктор для объекта имеют побочные эффекты.
Отключите его с помощью флага компилятора из комментария для просмотра такого поведения копирования)
Ответ 7
Я немного поиграл с ним, и синтаксис кажется довольно странным, когда конструктор не принимает аргументов. Позвольте мне привести пример:
#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() делает объект компилятора вы хотите создать указатель на функцию или что-то???!!