Практическое использование конструктора по умолчанию

Конструктор по умолчанию

Конструктор по умолчанию — это конструктор, не требующий аргументов, известный как конструктор без аргументов.

Когда компилятор сгенерирует для вас конструктор по умолчанию?

  • Если конструктор явно не объявлен
  • Если все члены данных и базовые классы могут быть построены по умолчанию

Давайте рассмотрим простой пример, как показано ниже

class ClassWithGeneratedDefaultConstructor { 
 public: 
  int m_value1; 
  double m_value2; 
  std::string m_value3; 
};

Когда мы пишем

ClassWithGeneratedDefaultConstructor example1;

Его можно скомпилировать, так как компилятор автоматически сгенерирует для нас конструктор по умолчанию. Если вы проверите m_value1 сейчас,

std::cout << example1.m_value1 << std::endl;

результат будет неинициализированным значением и может не быть тем, что вы ожидали, как 0.

Это связано с тем, что созданный компилятором конструктор по умолчанию по умолчанию создает элементы данных класса (определяемого пользователем), но не элементы данных фундаментального типа.

В этом примере вы получите значение "" для example1.m_value3, поскольку это тип std::string, который имеет конструктор по умолчанию, который обеспечивает значение по умолчанию "".

Инициализация члена данных

Теперь давайте поговорим об инициализации членов данных.

Одним из способов инициализации элементов данных является использование инициализатора конструктора или списка инициализаторов элементов в конструкторе, следуя рекомендациям CppCoreGuidelines C.49: Предпочтение инициализации присваиванию в конструкторах .

class ClassWithDefaultConstructor { 
 public: 
  ClassWithDefaultConstructor(): m_value1(0), m_value2(0.0), m_value3("") {}
  
  int m_value1;
  double m_value2;
  std::string m_value3; 
};

Мы также можем следовать C.48: предпочитать инициализаторы внутри класса инициализаторам членов в конструкторах для инициализаторов констант, чтобы использовать инициализаторы внутри класса для присвоения значения по умолчанию членам класса. Например,

class ClassWithDefaultConstructor { 
 public: 
  
  int m_value1{0};
  double m_value2{2.0};
  std::string m_value3{"Empty String"};
};

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

class ClassWithDefaultConstructor {
 public:
  ClassWithDefaultConstructor() = default;

  int m_value1{0};
  double m_value2{2.0};
  std::string m_value3{"Empty String"};
};

Явно заданный по умолчанию конструктор по умолчанию

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

В аналогичном примере, допустим, у нас есть

class PassengerInfo {
 public: 
  PassengerInfo() = default;
   
  std::string m_first_name{"First Name"};
  std::string m_last_name{"Last Name"};
  int m_age{20}; 
};

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

std::vector<PassengerInfo> passenger_info_vec(1000);
PassengerInfo passenger_info_arr[1000];

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

Явно удаленный конструктор по умолчанию

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

class PassengerInfo { 
  public:
    PassengerInfo() = delete;

    std::string m_first_name{"First Name"};
    std::string m_last_name{"Last Name"};
    int m_age{20};
};

В таком случае,

PassengerInfo passenger_info;

не смог бы скомпилировать.

ПРИМЕЧАНИЕ. Если у класса есть элементы данных, у которых есть удаленный конструктор по умолчанию, конструктор по умолчанию для класса также автоматически удаляется.

Создание объекта

Что, если мы создадим экземпляр объекта с помощью приведенного ниже кода?

PassengerInfo passenger_info();

Его можно скомпилировать, даже если конструктор по умолчанию удален! Почему? Это широко известно как наиболее неприятный синтаксический анализ, означающий, что компилятор считает, что строка является объявлением функции для функции, которая не принимает аргументов и возвращает объект типа PassengerInfo. . Поскольку это объявление функции, это означает, что следующий код, использующий его как объект, не сможет скомпилироваться.

PassengerInfo passenger_info(); 
std::cout << passenger_info.m_age << std::endl;

Следовательно, мы будем вызывать конструктор по умолчанию следующим образом.

PassengerInfo passenger_info;

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

Что, если мы напишем так?

PassengerInfo passenger_info{};

Он также сможет скомпилироваться!

Инициализация значения

Это связано с тем, что passenger_info{} вызывается как инициализация значения.

Помните самый первый пример в этом посте, где у нас есть

class ClassWithGeneratedDefaultConstructor {
  public:
    int m_value1; 
    double m_value2; 
    std::string m_value3; 
};

ClassWithGeneratedDefaultConstructor example1;
std::cout << example1.m_value1 << std::endl;

и значение m_value1 будет неинициализировано?

Если мы перепишем создание объекта с

ClassWithGeneratedDefaultConstructor example1{};

, мы получим 0 значение для m_value1.

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

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

class PassengerInfo {
 public:
  PassengerInfo() = delete;
  PassengerInfo(int age) : m_age(age) {}
  PassengerInfo(std::string first_name, std::string last_name, int age)
      : m_first_name(first_name), m_last_name(last_name), m_age(age) {}

  std::string m_first_name{"First Name"};
  std::string m_last_name{"Last Name"};
  int m_age{20};
};

Предыдущий способ построения объекта теперь будет иметь ошибку.

PassengerInfo passenger_info{};

Это связано с тем, что с определенными конструкторами мы должны создавать объект либо с помощью фигурных скобок для универсального синтаксиса инициализации

PassengerInfo passenger_info1{10};
PassengerInfo passenger_info2{"FirstName", "LastName", 30};

or

PassengerInfo passenger_info1(10);
PassengerInfo passenger_info2("FirstName", "LastName", 30);

ПРИМЕЧАНИЕ. Два конструктора добавлены только для иллюстрации. На самом деле они избыточны в соответствии с правилом списка инициализаторов C++11

Заключение

В этом посте мы рассмотрели большинство вещей, которые нам следует знать о конструкторе по умолчанию в C++, в том числе

  • Когда компилятор сгенерирует для вас конструктор по умолчанию?
  • Инициализация члена данных
  • Явно заданный по умолчанию конструктор по умолчанию
  • Явно удаленный конструктор по умолчанию
  • Создание объекта
  • Инициализация значения

Спасибо, что дочитали до конца этот пост.
Код доступен здесь. Надеюсь, что этот пост поможет вам лучше понять конструктор по умолчанию в C++.

Подпишитесь на DDIntel Здесь.

Посетите наш сайт здесь: https://www.datadriveninvestor.com

Присоединяйтесь к нашей сети здесь: https://datadriveninvestor.com/collaborate