Должен ли я использовать виртуальные функции Initialize () для инициализации объекта моего класса? - PullRequest
16 голосов
/ 24 июня 2011

В настоящее время я обсуждаю с моим учителем дизайн класса, и мы подошли к вопросу Initialize() функций, которые он активно продвигает. Пример:

class Foo{
public:
  Foo()
  { // acquire light-weight resources only / default initialize
  }

  virtual void Initialize()
  { // do allocation, acquire heavy-weight resources, load data from disk
  }

  // optionally provide a Destroy() function
  // virtual void Destroy(){ /*...*/ }
};

Все с дополнительными параметрами, конечно.

Теперь он также делает упор на расширяемость и использование в иерархиях классов (он разработчик игр, а его компания продает игровой движок) со следующими аргументами (дословно, только переведено):

Аргументы против конструкторов:

  • не может быть переопределено производными классами
  • не может вызывать виртуальные функции

Аргументы для Initialize() функций:

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

Меня всегда учили выполнять настоящую инициализацию непосредственно в конструкторе и не предоставляют такие Initialize() функции. Тем не менее, у меня наверняка не так много опыта, как у него, когда дело доходит до развертывания библиотеки / движка, поэтому я решил спросить у добрых стариков.

Итак, каковы аргументы за и против таких Initialize() функций? Зависит ли это от среды, где его следует использовать? Если да, просьба привести аргументы в пользу разработчиков библиотеки / движка или, если возможно, даже разработчика игр в целом.


Редактировать : Я должен был упомянуть, что такие классы будут использоваться как переменные-члены только в других классах, поскольку что-либо еще не имело бы для них смысла. К сожалению.

Ответы [ 13 ]

0 голосов
/ 04 июня 2014

Некоторые члены просто должны иметь значения при построении (например, ссылки, 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(), многие из вышеперечисленных проблем усугубляются.

0 голосов
/ 24 июня 2011

Если вы используете его, то вы должны сделать конструктор частным и использовать вместо него фабричные методы, которые вызывают для вас метод initialize(). Например:

class MyClass
{
public:
    static std::unique_ptr<MyClass> Create()
    {
        std::unique_ptr<MyClass> result(new MyClass);
        result->initialize();
        return result;
    }

private:
    MyClass();

    void initialize();
};

Тем не менее, методы инициализации не очень элегантны, но они могут быть полезны по точным причинам, которые сказал ваш учитель. Я бы не стал считать их «неправильными» как таковые. Если ваш дизайн хорош, то вы, вероятно, никогда не будете нуждаться в них. Однако реальный код иногда заставляет вас идти на компромиссы.

0 голосов
/ 24 июня 2011

Используйте функцию инициализации, только если у вас нет доступных данных на момент создания.

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...