Инициализация объекта C с помощью copy-list-initializer

#c #initialization #value-initialization

Вопрос:

 // Example program
#include <iostream>
#include <string>

class T{
public:   
    int x, y;
    T(){
        std::cout << "T() constr called..." << std::endl;
    };
    T(int x, int y):x(x),y(y){
        std::cout << "T(x,y) constr called..." << std::endl;
    }
    
    void inspect(){
        std::cout << "T.x: " << this->x << std::endl;
        std::cout << "T.y: " << this->y << std::endl;
    }
};

int main()
{
    T t1(5,6);
    t1.inspect();
    
    std::cout << std::endl;
    
    T t2 = {};
    t2.inspect();
}
 

Я получаю следующий результат:

 T(x,y) constr called...
T.x: 5
T.y: 6

T() constr called...
T.x: 208787120
T.y: 31385
 

Члены t2 экземпляра не были инициализированы нулем (чего я хотел добиться). Правильно ли я понимаю, что если у меня определен конструктор, он не будет выполнять нулевую инициализацию?
(Я знаю, как добиться инициализации до нуля, используя явные значения по умолчанию. Проблема в том, почему я не могу сделать это с помощью init-list)

Инициализация списка

В противном случае, если связанный список инициализации пуст, а T является типом класса с конструктором по умолчанию, выполняется инициализация значения.

Значение-инициализация

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

Aggregate-инициализация (кажется, это не мой случай, и поэтому он не инициализирует элементы нулем)

Агрегат представляет собой один из следующих типов:

  • тип класса (обычно struct или union), который имеет
    • нет объявленных пользователем конструкторов

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

Комментарии:

1. Это было бы с T() = defau< (и у вас был бы мусор (UB) с T t3; ), проще / безопаснее для инициализации int x = 0; int y = 0; .

2. Оффтопик: выбор имени класса T — плохой выбор. Он ничего не говорит о функциональности класса и T обычно ассоциируется с аргументом типа templates .

Ответ №1:

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

ДА.

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

  1. если T является типом класса без конструктора по умолчанию или с предоставленным пользователем или удаленным конструктором по умолчанию, объект инициализируется по умолчанию;
  2. если T является типом класса с конструктором по умолчанию, который не предоставляется пользователем и не удаляется (то есть это может быть класс с неявно определенным или используемым по умолчанию конструктором по умолчанию), объект инициализируется нулем, а затем инициализируется по умолчанию, если у него есть нетривиальный конструктор по умолчанию;

T содержит пользовательский конструктор по умолчанию, затем применяется #1 (но не # 2, сначала выполняющий нулевую инициализацию).

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

Ответ №2:

Элементы данных t2 имеют мусорное значение. Это связано с тем, что они имеют встроенный тип, и вы не инициализировали их явно. Решением было бы:

Решение 1. Используйте список инициализаторов конструктора

 T(): x(0), y(0){
        std::cout << "T() constr called..." << std::endl;
    };
 

Решение 2. используйте инициализатор в классе

 int x = 0, y = 0;
 

Вот почему рекомендуется, чтобы:

всегда инициализируйте встроенный тип в блочной / локальной области

Если вы используете любое из приведенных выше решений, результат будет:

 T(x,y) constr called...
T.x: 5
T.y: 6

T() constr called...
T.x: 0
T.y: 0
 

это то, что вы хотите, и его можно увидеть здесь и здесь .

Другим решением было бы использовать делегирующий конструктор (как предложено @MarekR в комментарии ниже), например:

 T():T(0, 0) 
{
    std::cout << "T() constr called..." << std::endl;
}
 

Комментарии:

1. Решение 3: конструктор делегирования: T():T(0, 0) {} .

2. @MarekR Да, я добавил это в конец своего ответа сейчас. Проверьте это.

Ответ №3:

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

 T(): x(0), y(0) {
    ...