Виклик конструкторів в c ++ без нових


142

Я часто бачив, що люди створюють об’єкти в C ++ за допомогою

Thing myThing("asdf");

Замість цього:

Thing myThing = Thing("asdf");

Це, здається, працює (використовуючи gcc), принаймні до тих пір, поки немає шаблонів. Моє запитання зараз, чи правильний перший рядок, і якщо так, чи слід його використовувати?


25
Будь-яка форма без нового.
Даніель Даранас

13
У другій формі буде використаний конструктор копіювання, так що ні, вони не еквівалентні.
Едвард Странд

Я трохи пограв з цим, здається, перший спосіб виходить з ладу іноді, коли шаблони використовуються з безпараметричними конструкторами ..
Nils

1
Ух, і я отримав знак "Приємного питання" за це, яка ганьба!
Нілс

Відповіді:


153

Обидва рядки насправді правильні, але роблять тонко різні речі.

Перший рядок створює новий об'єкт у стеку, викликаючи конструктор формату Thing(const char*).

Другий - трохи складніший. Він по суті робить наступне

  1. Створіть об’єкт типу Thingза допомогою конструктораThing(const char*)
  2. Створіть об’єкт типу Thingза допомогою конструктораThing(const Thing&)
  3. Зателефонуйте ~Thing()на об’єкт, створений на кроці №1

7
Я думаю, що такі дії є оптимізованими, а отже, суттєво не відрізняються за аспектами продуктивності.
М. Вільямс

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

1
Тож ви можете сказати, що другий рядок є невірним, оскільки він марнує ресурси без видимих ​​причин. Звичайно, можливо, що створення першої інстанції є навмисним для деяких побічних ефектів, але це ще гірше (стилістично).
МК.

3
Ні, @Jred, це не гарантується. Але навіть якщо компілятор вирішив виконати цю оптимізацію, конструктору копій все одно потрібно бути доступним (тобто не захищеним чи приватним), навіть якщо це не реалізовано та не викликається.
Роб Кеннеді

3
Здається, копія може бути усунена, навіть якщо у конструктора копіювання є побічні ефекти - дивіться мою відповідь: stackoverflow.com/questions/2722879/…
Дуглас Лідер

31

Я припускаю, що з другого рядка ви насправді маєте на увазі:

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

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

Навпаки, це:

Thing thing("uiae");

створює статичний об'єкт, який автоматично руйнується при виході з поточної області.


1
На жаль, це справді найпоширеніший спосіб створення нових динамічних об'єктів замість використання auto_ptr, unique_ptr або пов'язаних з ними.
Фред Нурк

3
Питання ОП було правильним, ця відповідь цілком стосується іншого питання (див. Відповідь @ JaredPar)
Silmathoron

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

Яка мета статичних ролях анулювати?
Стівен Хрест

1
@Stephen Уникайте попереджень про невикористані змінні.
Дуглас Лідер

10

Простіше кажучи, обидва рядки створюють об'єкт на стеці, а не на купі, як це робиться у «новому». Другий рядок насправді передбачає другий виклик конструктору копій, тому його слід уникати (його також потрібно виправити, як зазначено в коментарях). Ви повинні використовувати стек для невеликих об'єктів якомога більше, оскільки це швидше, однак якщо ваші об'єкти виживуть довше, ніж кадр стека, це очевидно неправильний вибір.


Для тих, хто не знайомий з різницею об'єктів, що створюють екземпляри в стеці, на відміну від нагромадження (тобто використання нового, а не використання нового ), ось хороша тема.
edmqkk

2

В ідеалі компілятор оптимізував би другий, але це не потрібно. Перший - найкращий спосіб. Однак, досить важливо зрозуміти різницю між стеком і купами в C ++, тому що ви повинні керувати власною пам’яттю купи.


Чи може компілятор гарантувати, що у конструктора копій немає побічних ефектів (наприклад, вводу / виводу)?
Стівен Крос

@Stephen - не має значення, чи конструктор копій робить I / O - дивіться мою відповідь 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

}

тому просто записування дужок myThing без дужок фактично викликає конструктор, тоді як Thing myThing () робить компілятор тим, що ви хочете створити вказівник функції чи щось таке !!


6
Це добре відома синтаксична неоднозначність у С ++. Коли ви пишете "int rand ()", компілятор не може знати, якщо ви маєте на увазі "створити int та за замовчуванням - ініціалізувати його" або "оголосити функцію rand". Правило полягає в тому, що він обирає останнє, коли це можливо.
jpalecek


2

У додаток до відповіді JaredPar

1-звичайний ctor, 2-х функціональний-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 описується термін експлуатації тимчасових об'єктів. ] [Примітка: явні виклики конструктора не дають значення, див. 3.10. ]


Де читати про RVO:

12 Спеціальні функції членів / 12.8 Копіювання об'єктів класу / Частина 15

Коли певні критерії виконуються, реалізація допускається опускати конструкцію копії об’єкта класу, навіть якщо конструктор копії та / або деструктор для об'єкта мають побічні ефекти .

Вимкніть його з прапорцем компілятора з коментаря, щоб переглянути таке поведінку копіювання)

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.