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

StackOverflow https://stackoverflow.com/questions/1632484

  •  06-07-2019
  •  | 
  •  

Вопрос

допустим, у меня есть 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 и т.д.всегда инициализируется значением defualt 0, а объектам присваивается значение null (по крайней мере, в большинстве случаев) Итак, каково правило в C ++??инициализируется ли объект defualt значением null, а примитивы - мусором?Конечно, я имею в виду переменные экземпляра.

Редактировать:

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

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

Это было полезно?

Решение

m_somemap - карта объектов

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

m_somebool

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

m_someint

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

Другие советы

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

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

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

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

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, или если бы мы хотели вызвать другой конструктор, отличный от конструктора по умолчанию, тогда нам нужно было бы это сделать.

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

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

T varName;

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

T* varName;

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

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

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

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

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

<Ол>
  • Вызовите конструкторы всех родительских виртуальных классов во всем дереве классов (в произвольном порядке)
  • Вызовите конструкторы всех непосредственно наследуемых родительских классов в порядке объявления
  • Вызовите конструкторы всех переменных-членов в порядке объявления
  • Существует несколько дополнительных особенностей, и некоторые компиляторы позволяют вам вытеснять некоторые вещи из этого конкретного порядка, но это общая идея. Для каждого из этих вызовов конструктора вы можете указать аргументы конструктора, в этом случае C ++ будет вызывать конструктор, как указано, или вы можете оставить его в покое, и C ++ попытается вызвать конструктор по умолчанию. По умолчанию используется конструктор без аргументов.

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

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

    Лицензировано под: CC-BY-SA с атрибуция
    Не связан с StackOverflow
    scroll top