инициализация структур/классов без конструкторов в стеке и куче

Я хотел бы знать правило обнуления структур (или классов), у которых нет конструктора по умолчанию в C++.

В частности, кажется, что если они хранятся в стеке (скажем, как локальная переменная), они неинициализируются, но если они размещены в куче, они инициализируются нулями (проверено с помощью GCC 4.9.1). Это гарантированно будет переносимым?

Пример программы:

#include <iostream>
#include <map>
using namespace std;

struct X {
    int i, j, k;
    void show() { cout << i << " " << j << " " << k << endl; }
};

int fib(int i) {
    return (i > 1) ? fib(i-1) + fib(i-2) : 1;
}

int main() {
    map<int, X> m;            
    fib(10);                  // fills the stack with cruft
    X x1;                     // local
    X &x2 = m[1];             // heap-allocated within map
    X *x3 = new X();          // explicitly heap-allocated
    x1.show();  // --> outputs whatever was on the heap in those positions
    x2.show();  // --> outputs 0 0 0 
    x3->show(); // --> outputs 0 0 0     
    return 0;
}

Отредактировано: удалено «или мне просто использовать конструктор» в выделенной жирным шрифтом части; потому что я спросил, что я хочу знать, является ли это гарантированным поведением или нет - мы все можем согласиться с тем, что читаемый код лучше с явными конструкторами.


person tucuxi    schedule 06.05.2015    source источник
comment
Я недостаточно знаю, чтобы опубликовать развернутый ответ, но new X() инициализирует X, а new X — нет (в данном случае, когда X — это совокупность)   -  person Quentin    schedule 06.05.2015
comment
Вы всегда должны писать конструктор и устанавливать внутренние переменные класса. Независимо от того, устанавливает ли их компилятор.   -  person maja    schedule 06.05.2015
comment
@Quentin Эти два утверждения одинаковы, но предпочтение следует отдавать второму (new X).   -  person maja    schedule 06.05.2015
comment
@maja они разные, и я бы предпочел new X(). Почему вы предпочитаете new X?   -  person TartanLlama    schedule 06.05.2015
comment
@maja они не такие. См. здесь, здесь и здесь. В этом случае X либо инициализируется значением (обнуляется), либо ничего не делается (тривиальный конструктор по умолчанию). В любом случае, правила инициализации - это кошмар, так что просто используйте явный конструктор и покончим с этим...   -  person Quentin    schedule 06.05.2015
comment
@TartanLlama Потому что у вас могут возникнуть проблемы в ситуациях, когда существует функция с именем X(). В этом случае компилятор будет интерпретировать X() как вызов функции, а new X всегда однозначен.   -  person maja    schedule 06.05.2015
comment
Вы ответили не тому человеку, @TartanLlama ;)   -  person Quentin    schedule 06.05.2015
comment
@Quentin: Тогда оставьте это тому, кто действительно знает достаточно! Не нужно отвечать/предполагать в комментариях; они не для этого. Ваше здоровье   -  person Lightness Races in Orbit    schedule 06.05.2015
comment
@maja: Полный бред. Отбрасывание () не избавит вас от этой двусмысленности!   -  person Lightness Races in Orbit    schedule 06.05.2015
comment
@Quentin Мы говорим о разных вещах? Код A* a = new A(); эквивалентен A* a = new A;, не так ли? Если конструктора нет, используется конструктор по умолчанию - неважно, опустили вы скобки или нет.   -  person maja    schedule 06.05.2015
comment
@maja: О чем ты говоришь? Это не двусмысленность синтаксического анализа в C++.   -  person Puppy    schedule 06.05.2015
comment
@maja: Нет. Нет, они разные. Отсюда вопрос и ответ.   -  person Puppy    schedule 06.05.2015
comment
@maja: Нет, это не эквивалентно. Как уже несколько раз объясняли несколько человек.   -  person Lightness Races in Orbit    schedule 06.05.2015
comment
@LightnessRacesinOrbit Если функция с таким именем существует, компилятор прервется с ошибкой.   -  person maja    schedule 06.05.2015
comment
@maja: Нажмите на ссылку, которую я вам дал. Тогда прочитайте его текст. Вы утверждаете, что удаление () каким-то образом позволяет избежать двусмысленности; Я продемонстрировал на живом примере, что это не так.   -  person Lightness Races in Orbit    schedule 06.05.2015
comment
@LightnessRacesinOrbit Я знал, что в моем комментарии, и ничего более;)   -  person Quentin    schedule 06.05.2015
comment
@LightnessRacesinOrbit Хм... Мне нужно проверить это, когда я дома... кажется, я что-то перепутал...   -  person maja    schedule 06.05.2015


Ответы (3)


Это не динамическое выделение, которое инициализирует нулями ваших struct участников; это ваш синтаксис:

X* ptr = new X();
//            ^^
// 
// as opposed to just:
X* ptr = new X;

Если вы хотите, чтобы это было гарантировано, просто продолжайте писать это. :)

Альтернативой, которая хорошо сочетается с эквивалентом автоматической длительности хранения, является использование более нового синтаксиса {}:

X* ptr = new X{};   // dynamic
X  obj{};           // automatic

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

person Lightness Races in Orbit    schedule 06.05.2015
comment
Хороший ответ; Я никогда не думал о вызове new без скобок для сравнения. Любые идеи относительно того, почему std::map инициализирует () мою структуру? - person tucuxi; 06.05.2015
comment
@tucuxi: Потому что это то, что он делает! [C++11: 23.4.4.3/1]: Эффекты: если в карте нет ключа, эквивалентного x, вставляет value_type(x, T()) в карту. Стандарт мог бы сделать все возможное, чтобы избежать этой инициализации значения и вместо этого использовать инициализацию по умолчанию, но это добавило бы сложности и, вероятно, уменьшило бы полезность. Я имею в виду, что в большинстве случаев полезнее иметь безопасно читаемое значение, чем избегать одной записи, которая может оказаться избыточной. - person Lightness Races in Orbit; 06.05.2015
comment
@tucuxi: Потому что не делать этого глупо. Вы просто получите кучу бесполезных значений, которые вы должны установить в любом случае впоследствии. - person Puppy; 06.05.2015
comment
@tucuxi Я не могу найти окончательного слова об этом [вставьте здесь стандартный вызов гуру], но IINM, просто берущий ссылку на неинициализированную переменную, является UB, и std::map должен возвращать такую ​​ссылку. - person Quentin; 06.05.2015
comment
@Quentin: Не могу сказать, что когда-либо слышал об этом, и я не могу найти никаких доказательств этого при [очень] быстром стандартном сканировании текста. Не то чтобы это меня сильно удивило. - person Lightness Races in Orbit; 06.05.2015

Всегда используйте конструктор, если вы хотите присвоить членам вашего класса определенные значения. Именно для этого и нужны конструкторы. Единственный раз, когда вам не нужно писать конструктор, который устанавливает нужные вам значения, это если язык предварительно предоставляет вам такой конструктор, как конструктор копирования. Конструктора по умолчанию нет в этом списке для int и других подобных типов, поэтому вам нужно добавить конструктор к вашему собственному типу, который устанавливает их соответствующим образом.

person Puppy    schedule 06.05.2015
comment
Это похоже на деспотический совет без оговорок. Добавление определяемого пользователем конструктора изменяет свойства типа так, как вам может не понадобиться в некоторых ситуациях. - person Lightness Races in Orbit; 06.05.2015
comment
извините, я отредактировал или я должен просто использовать конструктор, потому что мне действительно любопытно, почему все происходит именно так. Да, я согласен, что код был бы лучше с конструктором, но я хочу понять, почему сейчас он работает именно так - person tucuxi; 06.05.2015
comment
@Lightness: Любой, кто действительно сталкивается с одной из таких ситуаций и понимает, какие свойства изменены и почему, не нуждается в совете этого ответа, чтобы знать, что делать. Не писать конструкторы, когда они вам нужны по глупым причинам, гораздо чаще и намного хуже, чем случайно сделать ваш тип не-POD или что-то в этом роде. - person Puppy; 06.05.2015
comment
А, понятно: в этом ответе намеренно опущены важные детали, потому что любой, кто его читает, не поймет этих деталей. Академический снобизм во всей красе! - person Lightness Races in Orbit; 06.05.2015

Пока у вас нет никаких конструкторов, все члены являются общедоступными и нет никакого наследования, ваша структура/класс, скорее всего, является агрегатом.

Чтобы получить переносимую нулевую инициализацию, просто используйте X x = {};, которая выполняет нулевую инициализацию.

Вот стандартная цитата об агрегатах (8.5.1):

Агрегат представляет собой массив или класс (раздел 9) без конструкторов, предоставляемых пользователем (12.1), без инициализаторов скобок или равенства для нестатических элементов данных (9.2), без закрытых или защищенных нестатических элементов данных ( Раздел 11), без базовых классов (Статья 10) и без виртуальных функций (10.3).

ссылка: Почему я не могу инициализировать скобки структура, производная от другой структуры?

person timato    schedule 06.05.2015