Вызов конструкторов в c ++ без новых - PullRequest
125 голосов
/ 27 апреля 2010

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

Thing myThing("asdf");

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

Thing myThing = Thing("asdf");

Это похоже на работу (с использованием gcc), по крайней мере, до тех пор, пока не задействованы шаблоны. Мой вопрос сейчас, правильна ли первая строка, и если да, то должен ли я ее использовать?

Ответы [ 7 ]

133 голосов
/ 27 апреля 2010

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

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

Второй немного сложнее. Это по существу делает следующее

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

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

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

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

В отличие от этого:

Thing thing("uiae");

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

21 голосов
/ 27 апреля 2010

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

#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
10 голосов
/ 27 апреля 2010

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

2 голосов
/ 20 июня 2015

В добавлении к 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");
}

И вы увидите результат.

Из ИСО / МЭК 14882 2003-10-15

8,5, часть 12

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

12,1, часть 13

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


Где почитать про RVO:

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

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

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

2 голосов
/ 28 апреля 2010

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

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

2 голосов
/ 27 апреля 2010

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...