Вызов конструкторов в С++ без новых

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

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