Некоторые члены просто должны иметь значения при построении (например, ссылки, const
значения, объекты, разработанные для RAII без конструкторов по умолчанию) ... они не могут быть созданы в функции initialise()
, а некоторые не могут быть затем переназначен.
Так что, в общем случае, это не выбор конструктора по сравнению с initialise()
, это вопрос того, закончится ли в итоге код, разделенный между ними.
Баз и членов, которые могут быть инициализированы позже, для производного класса это означает, что они не private
; если вы зайдете так далеко, чтобы сделать базы / элементы не private
ради отсрочки инициализации, вы нарушите инкапсуляцию - один из основных принципов ООП. Нарушение инкапсуляции не позволяет разработчикам базовых классов рассуждать об инвариантах, которые класс должен защищать; они не могут разработать свой код, не рискуя взломать производные классы - которые они могут не увидеть.
В других случаях это возможно, но иногда неэффективно, если вам необходимо по умолчанию создать базу или член со значением, которое вы никогда не будете использовать, а затем вскоре назначьте ему другое значение. Оптимизатор может помочь - особенно если обе функции встроены и вызваны в быстрой последовательности - но не может.
- [конструкторы] не могут быть переопределены производными классами
... так что вы можете положиться на то, что они делают то, что нужно базовому классу ...
- [конструкторы] не могут вызывать виртуальные функции
CRTP позволяет производным классам внедрять функциональность - обычно это лучший вариант, чем отдельная подпрограмма initialise()
, которая работает быстрее.
Аргументы для функций Initialize ():
- производный класс может полностью заменить код инициализации
Я бы сказал, что это аргумент против, как указано выше.
- производный класс может выполнять инициализацию базового класса в любое время во время собственной инициализации
Это гибко, но рискованно - если базовый класс не инициализирован, производный класс может легко оказаться (из-за недосмотра во время эволюции кода) вызовом чего-то, что зависит от инициализации этой базы и, следовательно, завершится неудачей во время выполнения.
В более общем плане возникает вопрос надежного вызова, использования и обработки ошибок. При initialise
клиентский код должен не забывать вызывать его с ошибками, которые проявляются во время выполнения, а не во время компиляции. О проблемах можно сообщать, используя типы возврата вместо исключений или состояния, что иногда может быть лучше.
Если необходимо вызвать initialise()
, чтобы установить, скажем, указатель на nullptr
или безопасное для деструктора значение на delete
, но какой-то другой элемент данных или код выбрасывает первым, весь ад выпадает.
initialise()
также заставляет весь класс быть не-1062 * в клиентском коде, даже если клиент просто хочет создать начальное состояние и убедиться, что оно не будет изменено - в основном вы бросили const
- правильность из окна.
Код, выполняющий такие вещи, как p_x = new X(values, for, initialisation);
, f(X(values, for initialisation)
, v.push_back(X(values, for initialisation))
, будет невозможен - форсировать многословные и неуклюжие альтернативы.
Если также используется функция destroy()
, многие из вышеперечисленных проблем усугубляются.