Вызов конструкторов в c ++ без new


147

Я часто видел, что люди создают объекты на C ++, используя

Thing myThing("asdf");

Вместо этого:

Thing myThing = Thing("asdf");

Кажется, это работает (с использованием gcc), по крайней мере, до тех пор, пока не используются шаблоны. У меня вопрос: верна ли первая строка и стоит ли ее использовать?


27
Любая форма не нова.
Дэниел Даранас,

13
Вторая форма будет использовать конструктор копирования, поэтому нет, они не эквивалентны.
Эдвард Стрэндж

Я немного поигрался с этим, кажется, что первый способ иногда терпит неудачу, когда шаблоны используются с конструкторами без параметров ..
Нильс,

1
Ох, и я получил за это значок «Хороший вопрос», как жаль!
Nils

Ответы:


159

Обе строки на самом деле правильные, но делают несколько разные вещи.

Первая строка создает новый объект в стеке, вызывая конструктор формата Thing(const char*) .

Второй вариант немного сложнее. По сути, он делает следующее

  1. Создайте объект типа Thingс помощью конструктораThing(const char*)
  2. Создайте объект типа Thingс помощью конструктораThing(const Thing&)
  3. Вызов ~Thing()объекта, созданного на шаге №1

8
Я предполагаю, что эти типы действий оптимизированы и, следовательно, существенно не отличаются по производительности.
М. Уильямс

14
Я не думаю, что твои шаги правильные. Thing myThing = Thing(...)не использует оператор присваивания, он по-прежнему Thing myThing(Thing(...))создается копированием, как сказано , и не включает построение по умолчанию Thing(редактировать: сообщение было впоследствии исправлено)
AshleysBrain

1
Таким образом, вы можете сказать, что вторая строка неверна, потому что она тратит ресурсы без видимой причины. Конечно, возможно, что создание первого экземпляра сделано намеренно из-за каких-то побочных эффектов, но это даже хуже (стилистически).
МК.

3
Нет, @ Джаред, это не гарантируется. Но даже если компилятор решит выполнить эту оптимизацию, конструктор копирования все равно должен быть доступен (т.е. не защищен или не закрыт), даже если он не реализован или не вызван.
Роб Кеннеди,

3
Похоже, что копию можно опустить, даже если у конструктора копирования есть побочные эффекты - см. Мой ответ: stackoverflow.com/questions/2722879/…
Дуглас Лидер,

32

Я предполагаю, что во второй строке вы действительно имеете в виду:

Thing *thing = new Thing("uiae");

что было бы стандартным способом создания новых динамических объектов (необходимых для динамического связывания и полиморфизма) и сохранения их адреса в указателе. Ваш код делает то, что описал JaredPar, а именно создает два объекта (один передал a const char*, другой передал a const Thing&), а затем вызывает деструктор ( ~Thing()) для первого объекта (const char* ).

Напротив, это:

Thing thing("uiae");

создает статический объект, который автоматически уничтожается при выходе из текущей области видимости.


1
К сожалению, это действительно наиболее распространенный способ создания новых динамических объектов вместо использования auto_ptr, unique_ptr или связанных.
Fred Nurk

3
Вопрос ОП был правильным, этот ответ полностью касается другой проблемы (см. Ответ
@JaredPar

21

Компилятор может оптимизировать вторую форму до первой, но это не обязательно.

#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

Какова цель статического преобразования в void?
Стивен Кросс

1
@Stephen Избегайте предупреждений о неиспользуемых переменных.
Дуглас Лидер

10

Проще говоря, обе строки создают объект в стеке, а не в куче, как это делает «новый». Вторая строка фактически включает второй вызов конструктора копирования, поэтому его следует избегать (это также необходимо исправить, как указано в комментариях). Вы должны как можно чаще использовать стек для небольших объектов, так как он быстрее, однако, если ваши объекты будут жить дольше, чем кадр стека, то это явно неправильный выбор.


Для тех, кто не знаком с разницей между созданием экземпляров объектов в стеке и в куче (то есть с использованием new и без использования new ), вот хороший поток.
edmqkk

2

В идеале компилятор оптимизирует вторую, но это не обязательно. Первый - лучший способ. Однако очень важно понимать различие между стеком и кучей в C ++, так как вы должны управлять своей собственной памятью кучи.


Может ли компилятор гарантировать, что конструктор копирования не имеет побочных эффектов (таких как ввод-вывод)?
Стивен Кросс

@Stephen - не имеет значения, выполняет ли конструктор копирования ввод-вывод - см. Мой ответ stackoverflow.com/questions/2722879/…
Дуглас Лидер,

Хорошо, я вижу, компилятору разрешено превратить вторую форму в первую и тем самым избежать вызова конструктора копирования.
Стивен Кросс

2

Я немного поигрался с ним, и синтаксис кажется довольно странным, когда конструктор не принимает аргументов. Приведу пример:

#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 () делает компилятор, который вы хотите создать, указатель на функцию или что-то в этом роде ?? !!


6
Это хорошо известная синтаксическая неоднозначность в C ++. Когда вы пишете «int rand ()», компилятор не может знать, имеете ли вы в виду «создать int и инициализировать его по умолчанию» или «объявить функцию rand». Правило таково, что он выбирает последнее, когда это возможно.
jpalecek


2

В приложении к ответу 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

8.5, часть 12

Ваша 1-я, 2-я конструкция называется прямой инициализацией

12.1, часть 13

Преобразование типа функциональной записи (5.2.3) может использоваться для создания новых объектов этого типа. [Примечание: синтаксис выглядит как явный вызов конструктора. ] ... Созданный таким образом объект не имеет имени. [Примечание: 12.2 описывает время жизни временных объектов. ] [Примечание: явные вызовы конструктора не дают значений l, см. 3.10. ]


Где почитать про РВО:

12 Специальные функции-члены / 12.8 Копирование объектов класса / Часть 15

При соблюдении определенных критериев реализации разрешается опускать создание копии объекта класса, даже если конструктор копирования и / или деструктор для объекта имеют побочные эффекты .

Отключите его с помощью флага компилятора из комментария, чтобы просмотреть такое поведение копирования)

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.