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

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

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

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

    A::A() : m_someMap()
    

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

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

ИЗМЕНИТЬ:

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

A:: A(): m_someMap(), m_someint (0), m_somebool (false)

Ответ 1

m_somemap

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

m_somebool

  • Вы должны инициализировать его до true или false, если хотите, чтобы оно имело известное значение. Булевы - это "простые старые типы данных", и у них нет понятия конструктора. Кроме того, язык С++ не указывает значения по умолчанию для неявно инициализированных логических элементов.
  • Что вы получите, если опустите его: логический элемент с неопределенным значением. Вы не должны этого делать, а затем использовать его значение. Из-за этого настоятельно рекомендуется, чтобы вы инициализировали все значения этого типа.

m_someint

  • Вы должны инициализировать его до некоторого целочисленного значения, если хотите, чтобы оно имело известное значение. Целые являются "обычными старыми типами данных", и у них нет понятия конструктора. Более того, язык С++ не указывает значения по умолчанию для неявно инициализированных целых чисел.
  • Что вы получите, если опустите его: int member с неопределенным значением. Вы не должны этого делать, а затем использовать его значение. Из-за этого настоятельно рекомендуется, чтобы вы инициализировали все значения этого типа.

Ответ 2

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

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

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.
};

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

Но это так. Рассмотрим это:

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
    {}
};

Ответ 3

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

Ответ 4

A::A() : m_someMap()

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

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

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

то происходит, когда вызывается конструктор X:

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

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

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

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

Ответ 5

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

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

T varName;

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

T* varName;

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

Ответ 6

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

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

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

Ответ 7

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

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

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

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

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