Когда мне нужно использовать списки инициализаторов для инициализации членов класса C ++? - PullRequest
5 голосов
/ 27 октября 2009

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

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

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

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

Ответы [ 7 ]

12 голосов
/ 27 октября 2009

m_somemap

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

m_somebool

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

m_someint

  1. Вы должны инициализировать его каким-либо целочисленным значением, если хотите, чтобы оно имело известное значение. Целые числа являются «простыми старыми типами данных», и у них нет концепции конструктора. Более того, язык C ++ не определяет значения по умолчанию для неявно инициализированных целых чисел.
  2. Что вы получите, если пропустите это: элемент int с неопределенным значением. Вы не должны делать это, а затем использовать его значение. По этой причине настоятельно рекомендуется инициализировать все значения этого типа.
4 голосов
/ 27 октября 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
    {}
};
1 голос
/ 27 октября 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 is.
  • Что если эта последовательность вызова обоих конструктора по умолчанию и оператора присваивания слишком медленная? Возможно, для начала было бы гораздо эффективнее просто вызвать правильный конструктор.

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

1 голос
/ 27 октября 2009

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

0 голосов
/ 27 октября 2009

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

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

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

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

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

0 голосов
/ 27 октября 2009

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

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

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

0 голосов
/ 27 октября 2009

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

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

T varName;

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

T* varName;

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

...