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

Предположим, что у меня есть класс с частными memebers 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? Что происходит с указателями? Do string и int получают 0-intialized с конструкторами по умолчанию string() и int()? Как насчет ссылочного члена? Также как насчет ссылок const?

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

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

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

Ответ 1

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

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

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

Для ссылок (например, 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

Ответ 2

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

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

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

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

Если вы не укажете mem-инициализатор элемента, в стандарте С++ говорится:

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

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

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

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

Ответ 3

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

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

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

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

Ответ 4

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

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

Я использую эту форму в значительной степени исключительно, хотя я читал, что некоторые считают ее "плохой формой", возможно, потому, что она была введена только недавно - я думаю, в С++ 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;
};

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

Ответ 5

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

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

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

int *ptr; // will point to a random memory location
string name; // empty string (due to string 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

Ответ 6

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

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

Ответ 7

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

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