Как члены класса C ++ инициализируются, если я не делаю это явно?


158

Предположим , у меня есть класс с частными ЧЛЕНОВ ptr, name, pname, rname, crnameи age. Что произойдет, если я сам их не инициализирую? Вот пример:

class Example {
    private:
        int *ptr;
        string name;
        string *pname;
        string &rname;
        const string &crname;
        int age;

    public:
        Example() {}
};

И тогда я делаю:

int main() {
    Example ex;
}

Как члены инициализируются в ex? Что происходит с указателями? Делать stringи intполучать 0-Instalized с конструкторами по умолчанию string()и int()? Как насчет референтного члена? А как насчет постоянных ссылок?

О чем еще я должен знать?

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

Я хотел бы изучить это, чтобы я мог писать лучше (без ошибок) программ. Любой отзыв поможет!


3
Для рекомендаций книги см. Stackoverflow.com/questions/388242/…
Майк Сеймур

Майк, я имею в виду главу из какой-то книги, которая объясняет это. Не вся книга! :)
bodacydo

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

2
Скотт Мейерс (Скотт Мейерс) (популярный бывший гуру-консультант по C ++) утверждает в Effective C ++ : «Правила сложны - на мой взгляд, слишком сложны, чтобы их стоило запомнить, убедитесь, что все конструкторы инициализируют все в объекте». Так что, по его мнению, самый простой способ (попытка) написать «безошибочный» код - это не попытаться запомнить правила (а на самом деле он не выкладывает их в книге), а явно инициализировать все. Тем не менее, обратите внимание, что даже если вы используете этот подход в своем собственном коде, вы можете работать над проектами, написанными людьми, которые этого не делают, поэтому правила все равно могут быть полезны.
Кайл Стрэнд

2
@TylerMcHenry Какие книги по C ++ вы считаете «хорошими»? Я прочитал несколько книг на C ++, но ни одна из них не объяснила это полностью. Как отмечалось в моем предыдущем комментарии, Скотт Мейерс явно отказывается предоставить полные правила в Effective C ++ . Я также читал « Эффективный современный С ++» Мейерса , « Общие знания» Дьюхерста « С ++» и « Путешествие по С ++» Страуструпа . Насколько я помню, никто из них не объяснил полные правила. Очевидно, я мог бы прочитать стандарт, но вряд ли считаю это «хорошей книгой»! : D И я ожидаю, что Страуструп, вероятно, объясняет это на языке программирования C ++ .
Кайл Стрэнд

Ответы:


207

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

Для объектов вызывается их конструктор по умолчанию. Например, для std::stringконструктора по умолчанию устанавливается пустая строка. Если класс объекта не имеет конструктора по умолчанию, это будет ошибкой компиляции, если вы явно не инициализируете его.

Для примитивных типов (указатели, целые и т. Д.) Они не инициализируются - они содержат любой произвольный мусор, который раньше находился в этом месте памяти.

Что касается ссылок (например std::string&), запрещается их не инициализировать, и ваш компилятор будет жаловаться и отказываться компилировать такой код. Ссылки всегда должны быть инициализированы.

Итак, в вашем конкретном случае, если они не инициализированы явно:

    int *ptr;  // Contains junk
    string name;  // Empty string
    string *pname;  // Contains junk
    string &rname;  // Compile error
    const string &crname;  // Compile error
    int age;  // Contains junk

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

7
«Если класс объекта не имеет конструктора по умолчанию, это будет ошибка компиляции, если вы явно не инициализируете его», это неправильно ! Если класс не имеет конструктора по умолчанию, ему предоставляется конструктор по умолчанию, который является пустым.
Волшебник

19
@wiz Я думаю, что он буквально имел в виду «если объект не имеет конструктора по умолчанию», как и в случае не сгенерированного, что было бы так, если бы класс явно определял любые конструкторы, отличные от заданных по умолчанию (ctor по умолчанию не будет сгенерирован). Если мы станем слишком педанатичными, мы, вероятно, запутаемся больше, чем помощью, и Тайлер в своем ответе передо мной высказался об этом.
stinky472

8
@ wiz-loz Я бы сказал, что у fooнего есть конструктор, он просто неявный. Но это действительно аргумент семантики.
Тайлер МакГенри

4
Я интерпретирую «конструктор по умолчанию» как конструктор, который можно вызывать без аргументов. Это будет либо тот, который вы определяете сами, либо неявно генерируемый компилятором. Таким образом, отсутствие этого означает, что вы не определены и не созданы. Или вот как я это вижу.
5ound

28

Сначала позвольте мне объяснить, что такое mem-initializer-list . Список MEM-инициализатор- это разделенный запятыми список MEM-инициализатор с, где каждый MEM-инициализатор представляет собой имя элемента с последующим (, с последующим выражением-лист , за которым следует ). Список выражений - это то, как создается член. Например, в

static const char s_str[] = "bodacydo";
class Example
{
private:
    int *ptr;
    string name;
    string *pname;
    string &rname;
    const string &crname;
    int age;

public:
    Example()
        : name(s_str, s_str + 8), rname(name), crname(name), age(-4)
    {
    }
};

ает-инициализатор-лист конструктора предоставленного пользователя, не-аргументы name(s_str, s_str + 8), rname(name), crname(name), age(-4). Этот MEM-инициализатор-списка означает , что nameэлемент инициализируется в std::stringконструкторе , который принимает два входных итераторов , то rnameэлемент инициализируется со ссылкой name, то crnameэлемент инициализируется с константной-ссылкой name, а ageэлемент инициализируются со значением -4.

Каждый конструктор имеет свой собственный список mem-initializer-list , и члены могут быть инициализированы только в установленном порядке (в основном, в порядке, в котором члены объявлены в классе). Таким образом, члены Exampleмогут быть инициализированы только в следующем порядке: ptr, name, pname, rname, crname, и age.

Когда вы не указываете mem-инициализатор члена, стандарт C ++ говорит:

Если сущность является нестатическим элементом данных ... типа класса ..., сущность инициализируется по умолчанию (8.5). ... В противном случае сущность не инициализируется.

Здесь, поскольку nameэто нестатический член данных типа класса, он инициализируется по умолчанию, если nameв списке mem-initializer-list не указан инициализатор для . Все остальные члены класса Exampleне имеют типа класса, поэтому они не инициализируются.

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

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


Это лучший способ инициализировать элементы, когда вы хотите строго разделить объявление (in .h) и определение (in .cpp), не показывая слишком много внутренних элементов .
Матье

12

Вы также можете инициализировать элементы данных в том месте, где вы их объявили:

class another_example{
public:
    another_example();
    ~another_example();
private:
    int m_iInteger=10;
    double m_dDouble=10.765;
};

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

Еще один полезный аспект новых правил - как инициализировать данные-члены, которые сами являются классами. Например, предположим, что CDynamicStringэто класс, который инкапсулирует обработку строк. Он имеет конструктор, который позволяет указать его начальное значение CDynamicString(wchat_t* pstrInitialString). Вы могли бы очень хорошо использовать этот класс в качестве члена данных внутри другого класса - скажем, класса, который инкапсулирует значение реестра Windows, которое в этом случае хранит почтовый адрес. Чтобы «жестко запрограммировать» имя раздела реестра, в которое это записывается, вы используете фигурные скобки:

class Registry_Entry{
public:
    Registry_Entry();
    ~Registry_Entry();
    Commit();//Writes data to registry.
    Retrieve();//Reads data from registry;
private:
    CDynamicString m_cKeyName{L"Postal Address"};
    CDynamicString m_cAddress;
};

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


9

Если ваш пример класса создается в стеке, содержимое неинициализированных скалярных членов является случайным и неопределенным.

Для глобального экземпляра неинициализированные скалярные члены будут обнулены.

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

  • int *ptr; // неинициализированный указатель (или обнуляется, если глобальный)
  • string name; // вызывается конструктор, инициализируется пустой строкой
  • string *pname; // неинициализированный указатель (или обнуляется, если глобальный)
  • string &rname; // ошибка компиляции, если вам не удалось инициализировать это
  • const string &crname; // ошибка компиляции, если вам не удалось инициализировать это
  • int age; // скалярное значение, неинициализированное и случайное (или обнуляемое, если оно глобально)

Я экспериментировал, и кажется, что string nameон пуст после инициализации класса в стеке. Вы абсолютно уверены в своем ответе?
бодасидо

1
строка будет иметь конструктор, который по умолчанию предоставляет пустую строку - я уточню свой ответ
Пол Диксон

@bodacydo: Пол прав, но если вы заботитесь об этом поведении, вам никогда не помешает быть явным. Брось его в список инициализатора.
Стивен

Спасибо за разъяснения и объяснения!
бодасидо

2
Это не случайно! Случайное слово слишком велико для этого! Если скалярные члены будут случайными, нам не понадобятся другие генераторы случайных чисел. Представьте себе программу, которая анализирует данные «остатки» - такие как восстановленные файлы в памяти - данные далеко не случайны. Это даже не неопределенно! Обычно это трудно определить, потому что обычно мы не знаем, что делает наша машина. Если те «случайные данные», которые вы только что восстановили, являются единственным изображением вашего отца, ваша мать может даже найти это оскорбительным, если вы скажете, что это случайные данные ...
slyy2048

5

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

Конечно, для параметров объекта (например string) конструктор объекта может выполнить инициализацию по умолчанию.

В вашем примере:

int *ptr; // will point to a random memory location
string name; // empty string (due to string's default costructor)
string *pname; // will point to a random memory location
string &rname; // it would't compile
const string &crname; // it would't compile
int age; // random value

2

У членов с конструктором будет вызываться конструктор по умолчанию для инициализации.

Вы не можете зависеть от содержимого других типов.


0

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

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


0

Это зависит от того, как построен класс

Ответ на этот вопрос дает понимание огромного оператора case switch в стандарте языка C ++, и простому смертному трудно понять интуицию.

В качестве простого примера того, как сложная вещь:

main.cpp

#include <cassert>

int main() {
    struct C { int i; };

    // This syntax is called "default initialization"
    C a;
    // i undefined

    // This syntax is called "value initialization"
    C b{};
    assert(b.i == 0);
}

При инициализации по умолчанию вы начинаете с: https://en.cppreference.com/w/cpp/language/default_initialization, мы переходим к части «Эффекты инициализации по умолчанию» и запускаем оператор case:

  • «если T не POD »: нет (само определение POD само по себе является огромным оператором переключения)
  • «если T - тип массива»: нет
  • «в противном случае ничего не делается»: поэтому он остается с неопределенным значением

Затем, если кто-то решит инициализировать значение, мы перейдем на https://en.cppreference.com/w/cpp/language/value_initialization «Эффекты инициализации значения есть» и запустим оператор case:

  • «если T является типом класса без конструктора по умолчанию или с предоставленным пользователем или удаленным конструктором по умолчанию»: это не так. Теперь вы потратите 20 минут, прибегая к помощи этих терминов:
    • у нас есть неявно определенный конструктор по умолчанию (в частности, потому что другой конструктор не был определен)
    • он не предоставляется пользователем (неявно определяется)
    • не удаляется ( = delete)
  • «если T - это тип класса с конструктором по умолчанию, который не предоставлен и не удален пользователем»: да
    • «объект инициализируется нулями, а затем инициализируется по умолчанию, если у него есть нетривиальный конструктор по умолчанию»: нет нетривиального конструктора, просто инициализация нулями. Определение «инициализации нуля», по крайней мере, простое и делает то, что вы ожидаете: https://en.cppreference.com/w/cpp/language/zero_initialization

Вот почему я настоятельно рекомендую вам никогда не полагаться на «неявную» нулевую инициализацию. Если нет веских причин для производительности, явно инициализируйте все, либо на конструкторе, если вы его определили, либо с помощью агрегатной инициализации. В противном случае вы делаете вещи очень и очень рискованными для будущих разработчиков.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.