Должен ли я использовать виртуальные функции 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 ]

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

Для Initialize: именно то, что говорит ваш учитель, но в хорошо продуманном коде он вам, вероятно, никогда не понадобится.

Против: нестандартный, может отрицательно сказаться на цели конструктора, если его использовать не по назначению,Что еще более важно: клиент должен помнить, чтобы позвонить Initialize.Таким образом, либо экземпляры будут находиться в несогласованном состоянии при создании, либо им нужно много дополнительной бухгалтерии, чтобы код клиента не вызывал что-либо еще:

void Foo::im_a_method()
{
    if (!fully_initialized)
        throw Unitialized("Foo::im_a_method called before Initialize");
    // do actual work
}

Единственный способ предотвратить этот вид кода - запуститьиспользуя заводские функции.Итак, если вы используете Initialize в каждом классе, вам понадобится фабрика для каждой иерархии.

Другими словами: не делайте этого, если в этом нет необходимости;всегда проверяйте, можно ли изменить код в терминах стандартных конструкций.И, конечно, не добавляйте члена public Destroy, это задача деструктора.В любом случае деструкторы могут (и в случаях наследования) должны быть virtual.

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

Это ужасная, ужасная идея.Спросите себя - какой смысл конструктору, если вам просто нужно позвонить Initialize() позже?Если производный класс хочет переопределить базовый класс, то не выводит .

Когда конструктор завершает работу, имеет смысл использовать объект.Если это не так, вы сделали это неправильно.

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

Я против 'двойной инициализации' в C ++ вообще.

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

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

Если вам нужно написать такой код, это означает, что ваш дизайн неправильный (например, MFC). Разработайте ваш базовый класс так, чтобы вся необходимая информация, которая может быть переопределена, передавалась через параметры его конструктора, чтобы производный класс мог переопределить его следующим образом:

Derived::Derived() : Base(GetSomeParameter()) 
{
}
4 голосов
/ 24 июня 2011

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

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

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

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

Здесь есть голос раздора.

  1. Возможно, вы работаете в среде, где у вас нет выбора, кроме как разделить конструкцию и инициализацию. Добро пожаловать в мой мир. Не говорите мне, чтобы найти другую среду; У меня нет выбора. Предпочтительный вариант продуктов, которые я создаю, не в моих руках.

  2. Расскажите, как инициализировать некоторые аспекты объекта B относительно объекта C, другие аспекты относительно объекта A; некоторые аспекты объекта C по отношению к объекту B, другие аспекты по отношению к объекту A. В следующий раз ситуация может быть полностью изменена. Я даже не буду вдаваться в то, как инициализировать объект A. Очевидно, что круговые зависимости инициализации могут быть разрешены, но не конструкторами.

  3. Аналогичные проблемы касаются уничтожения и остановки. Возможно, объекту необходимо прожить после завершения работы, его, возможно, потребуется повторно использовать в целях Монте-Карло, и его может потребоваться перезапустить с контрольно-пропускного пункта, сброшенного три месяца назад. Поместить весь код освобождения непосредственно в деструктор - очень плохая идея, потому что он утекает.

1 голос
/ 24 июня 2011

Другие долго спорили против использования Initialize, я сам вижу одно применение: лень.

Например:

File file("/tmp/xxx");
foo(file);

Теперь, если foo никогда не использует file (в конце концов), тогда совершенно не нужно пытаться читать его (и это действительно будет пустой тратой ресурсов).

В этой ситуации я поддерживаю Ленивая инициализация , однако она не должна полагаться на клиент, вызывающий функцию, а скорее каждая функция-член должна проверять, необходимо инициализировать или нет. В этом примере name() не требует этого, но encoding() делает.

1 голос
/ 24 июня 2011

Игнорируя последствия RAII, которые адекватно освещены другими, метод виртуальной инициализации значительно усложняет ваш дизайн.Вы не можете иметь никаких личных данных, потому что для того, чтобы возможность переопределить процедуру инициализации была вообще полезной, производный объект должен иметь к ней доступ.Таким образом, теперь инварианты класса должны поддерживаться не только классом, но и каждым классом, который наследует его.Во-первых, избежание такого бремени является частью проблемы наследования, и конструкторы причин работают так же, как и в случае создания подобъекта.

1 голос
/ 24 июня 2011

Не звонить Initialize может быть легко сделать случайно и не даст вам правильно построенный объект.Он также не следует принципу RAII, поскольку существуют отдельные этапы построения / разрушения объекта: что произойдет, если Initialize не удастся (как вы поступите с недопустимым объектом)?

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

1 голос
/ 24 июня 2011

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

Производный класс должен не полностью переопределять поведение инициализации базового класса. Это конструктивный недостаток, который следует вылечить, а не вводить Initialize() -функции в качестве обходного пути.

1 голос
/ 24 июня 2011

Забудьте о функции Initialize() - это работа конструктора.

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

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