Когда мне нужно использовать списки инициализаторов для инициализации членов класса C ++?

скажем, у меня std::map< std::string, std::string > m_someMap как частная переменная-член класса A

Два вопроса: (и единственная причина, по которой я спрашиваю, это то, что я наткнулся на такой код)

  1. Какова цель этой строки:

    A::A() : m_someMap()
    

    Теперь я знаю, что это инициализация, но нужно ли это делать вот так? Я смущен.

  2. Какое значение по умолчанию для std::map< std::string, std::string > m_someMap, также C # определяет, что int, double и т. Д. Всегда инициализируются значением по умолчанию 0, а объекты имеют значение null (по крайней мере, в большинстве случаев). Итак, какое правило в C ++ ?? объект инициализируется по умолчанию нулевым значением, а примитивы - мусором? Конечно, я говорю о переменных экземпляра.

РЕДАКТИРОВАТЬ:

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

A :: A (): m_someMap (), m_someint (0), m_somebool (ложь)


person Tom    schedule 27.10.2009    source источник
comment
См. Подробные правила инициализации по умолчанию в C ++: stackoverflow.com/questions/1628311/ инициализация массива /   -  person Pavel Minaev    schedule 27.10.2009


Ответы (7)


m_somemap

  1. Вы не обязаны.
  2. Что вы получите, если опустите его: пустой std::map< std::string, std::string >, то есть действительный экземпляр этой карты, в котором нет элементов.

m_somebool

  1. Вы должны инициализировать его true или false, если хотите, чтобы оно имело известное значение. Логические значения - это «простые старые типы данных», и они не имеют концепции конструктора. Более того, язык C ++ не определяет значения по умолчанию для неявно инициализированных логических значений.
  2. Что вы получите, если его опустите: логический член с неопределенным значением. Вы не должны делать этого, а затем использовать его значение. По этой причине настоятельно рекомендуется инициализировать все значения этого типа.

m_someint

  1. Вы должны инициализировать его некоторым целочисленным значением, если хотите, чтобы оно имело известное значение. Целые числа - это «простые старые типы данных», и они не имеют концепции конструктора. Более того, язык C ++ не определяет значения по умолчанию для неявно инициализированных целых чисел.
  2. Что вы получите, если его опустите: член типа int с неопределенным значением. Вы не должны делать этого, а затем использовать его значение. По этой причине настоятельно рекомендуется инициализировать все значения этого типа.
person Daniel Daranas    schedule 27.10.2009
comment
Вторую часть можно было бы лучше сформулировать как «она вызывает конструктор по умолчанию для :: std :: map‹ :: std :: string, :: std :: string ›». И первое можно сформулировать так, чтобы сказать, что это не обязательно, но это выбор стиля. Лично я считаю, что он «плохой / хороший» ... (я сам думаю, что это глупо и бессмысленно.) - person Omnifarious; 27.10.2009
comment
Ой, моя ошибка во второй части. Вы все правильно поняли. - person Omnifarious; 27.10.2009
comment
Я думал, что ваша фраза лучше, и я ее уже отредактировал! Вернул обратно в оригинал. Что касается первой части, вам не обязательно уже подразумевать, что вы можете сделать это, если хотите, в этом просто нет необходимости. В противном случае Вы не должны этого делать, иначе Вы не сможете, это будет использовано, что не так. - person Daniel Daranas; 27.10.2009
comment
как насчет: m_someMap (), m_someint (0), m_somebool (false) (прочтите РЕДАКТИРОВАТЬ) - person Tom; 27.10.2009
comment
@ Мартин Йорк: Я перефразировал это. Я, конечно, не применяю к ним термин «объекты», хотя официально они могут быть объектами в терминологии стандарта C ++, чего я не знаю. - person Daniel Daranas; 27.10.2009

На самом деле в этом нет необходимости.
Конструктор по умолчанию сделает это автоматически.

Но иногда, делая это явным, он действует как своего рода документация:

class X
{
    std::map<string,string>  data;
    Y                        somePropertyOfdata;

    X()
      :data()                    // Technically not needed
      ,somePropertyOfdata(data)  // But it documents that data is finished construction
    {}                           // before it is used here.
};

Правило C ++ состоит в том, что, если вы явно не инициализируете данные POD, они не определены, в то время как другие классы имеют конструктор по умолчанию, вызываемый автоматически (даже если это явно не сделано программистом).

Но говоря это. Учти это:

template<typename T>
class Z
{
     T  data;   
     Z()
        :data()    // Technicall not need as default constructor will
                   // always be called for classes.
                   // But doing this will initialize POD data correctly
                   // if T is a basic POD type. 
     {}
};

Здесь вы ожидаете, что данные будут инициализированы по умолчанию.
Технически POD не имеет конструкторов, поэтому, если бы T был int, ожидали бы вы, что он что-нибудь сделает? Поскольку он был явно инициализирован, он установлен в 0 или эквивалент для типов POD.

Для редактирования:

class A
{
    std::map<string,string>   m_someMap;
    int                       m_someint;
    bool                      m_somebool;
   public:
    A::A()
       : m_someMap()      // Class will always be initialised (so optional)
       , m_someint(0)     // without this POD will be undefined
       , m_somebool(false)// without this POD will be undefined
    {}
};
person Martin York    schedule 27.10.2009
comment
Будьте осторожны, чтобы избежать проблем с порядком инициализации, если у вас есть взаимозависимые члены. Если порядок объявлений для data и somePropertyOfData был изменен в первом примере, то вы вызываете носовых демонов. - person D.Shawley; 27.10.2009
comment
@ D, Шоули: Верно. Но любой хороший компилятор выдаст соответствующие предупреждения. И если вы хороший программист, вы устанавливаете компилятор, чтобы он сообщал обо всех предупреждениях как об ошибках, и поэтому он не будет компилироваться. - person Martin York; 27.10.2009
comment
Вау, правило явного вызова конструктора по умолчанию для типа POD очень странное. Что произойдет, если T - это класс с автоматически сгенерированным конструктором по умолчанию? - person Omnifarious; 27.10.2009
comment
@Omnifarious: как вы думаете, что происходит? Конструктор по умолчанию вызывается всегда, даже если он создан компилятором. - person Martin York; 27.10.2009
comment
В последнем примере я бы предпочел сказать undefined over (т.е. случайный). Рэндом предполагает, что это могло быть как-то полезно. Может быть, было бы полезно добавить некоторую информацию о разнице между статическими и константными POD. - person pmr; 27.10.2009

Как отмечали другие: это не обязательно, но более или менее вопрос стиля. Плюс: он показывает, что вы явно хотите использовать конструктор по умолчанию, и делает ваш код более подробным. Обратная сторона: если у вас более одного ctor, может быть сложно поддерживать изменения во всех из них, и иногда вы добавляете членов класса и забываете добавить их в список инициализаторов ctors, чтобы он выглядел несогласованным.

person pmr    schedule 27.10.2009

A::A() : m_someMap()

В этом случае эта строка не нужна. Однако, в целом, это единственный правильный способ инициализировать члены класса.

Если у вас есть такой конструктор:

X() : y(z) {
 w = 42;
}

то при вызове конструктора X происходит следующее:

  • Сначала инициализируются все члены: для y мы явно говорим, что хотим вызвать конструктор, который принимает z в качестве аргумента. Для w то, что происходит, зависит от типа w. Если w является типом POD (то есть в основном C-совместимым типом: без наследования, без конструкторов или деструкторов, все члены являются общедоступными и все члены также являются типами POD), то это не инициализирован. Его начальное значение - это мусор, обнаруженный по этому адресу памяти. Если w не является типом POD, то вызывается его конструктор по умолчанию (типы, отличные от POD, всегда инициализируются при построении).
  • После создания обоих членов мы затем вызываем оператор присваивания, чтобы присвоить 42 w.

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

  • Что, если w относится к типу, не имеющему конструктора по умолчанию? Тогда это не будет компилироваться. Затем он должен быть явно инициализирован после :, как y.
  • Что, если эта последовательность вызова и конструктора по умолчанию и оператора присваивания будет излишне медленной? Возможно, было бы намного эффективнее для начала просто вызвать правильный конструктор.

Короче говоря, поскольку m_someMap не является типом POD, нам, строго говоря, не нужно делать : m_someMap(). В любом случае он был бы построен по умолчанию. Но если бы это был тип POD или если бы мы хотели вызвать другой конструктор, отличный от конструктора по умолчанию, то нам пришлось бы это сделать.

person jalf    schedule 27.10.2009
comment
что такое POD в любом случае - person slier; 02.05.2017

Просто чтобы прояснить, что происходит (в отношении вашего второго вопроса)

std::map< std::string, std::string > m_someMap создает переменную стека с именем m_someMap, и для нее вызывается конструктор по умолчанию. Правило C ++ для всех ваших объектов:

T varName;

где T - тип, по умолчанию создается varName.

T* varName;

должно быть явно присвоено NULL (или 0) - или nullptr в новом стандарте.

person Lou Franco    schedule 27.10.2009
comment
Для T varName он не всегда создается по умолчанию - его не будет, если T, скажем, int или struct Foo { int Bar; };. - person Pavel Minaev; 27.10.2009

Чтобы прояснить проблему со значением по умолчанию:

В C ++ нет концепции имплицитности некоторых типов по ссылке. Если что-то явно не объявлено как указатель, оно не может никогда принимать нулевое значение. Это означает, что каждый класс будет иметь конструктор по умолчанию для построения начального значения, когда не указаны параметры конструктора. Если конструктор по умолчанию не объявлен, компилятор сгенерирует его за вас. Кроме того, всякий раз, когда класс содержит члены классифицированных типов, эти члены будут неявно инициализированы через их собственные конструкторы по умолчанию при создании объекта, если вы не используете синтаксис двоеточия для явного вызова другого конструктора.

Так уж получилось, что конструктор по умолчанию для всех типов контейнеров STL создает пустой контейнер. Другие классы могут иметь другие соглашения о том, что делают их конструкторы по умолчанию, поэтому вы все равно хотите знать, что они вызываются в подобных ситуациях. Вот почему строка A::A() : m_someMap(), которая на самом деле просто говорит компилятору сделать то, что он уже сделал бы.

person Walter Mundt    schedule 27.10.2009

Когда вы создаете объект на C ++, конструктор выполняет следующую последовательность:

  1. Вызвать конструкторы всех родительских виртуальных классов во всем дереве классов (в произвольном порядке)
  2. Вызов конструкторов всех напрямую наследуемых родительских классов в порядке объявления
  3. Вызов конструкторов всех переменных-членов в порядке объявления

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

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

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

person Jason E    schedule 27.10.2009