Вызов виртуальных методов в C ++ запрещен, однако в некоторых ситуациях это может быть очень полезно.
Рассмотрим следующую ситуацию — пара классов Parent и Child. Родительский конструктор требует создания экземпляра класса Child, потому что он должен инициализировать его особым образом. И Родитель и Дочерний могут быть производными, так что DerivedParent использует DerivedChild.
Однако есть проблема — потому что в вызове Parent :: ctor из DerivedParent :: ctor базовый класс должен иметь экземпляр DerivedChild вместо Child. Но это потребует вызова виртуального метода каким-то образом, что запрещено. Я говорю о чем-то вроде этого:
class Child
{
public:
virtual std::string ToString() { return "Child"; }
};
class DerivedChild : public Child
{
public:
std::string ToString() { return "DerivedChild"; }
};
class Parent
{
protected:
Child * child;
virtual Child * CreateChild() { return new Child(); }
public:
Parent() { child = CreateChild(); }
Child * GetChild() { return child; }
};
class DerivedParent : public Parent
{
protected:
Child * CreateChild() { return new DerivedChild(); }
};
int main(int argc, char * argv[])
{
DerivedParent parent;
printf("%s\n", parent.GetChild()->ToString().c_str());
getchar();
return 0;
}
Давайте рассмотрим пример из реальной жизни. Предположим, что я хочу написать оболочку для окон WinApi. Базовый класс Control должен зарегистрировать класс и создать экземпляр окна (например, RegisterClassEx и CreateWindowEx), чтобы правильно его настроить (например, зарегистрировать класс таким образом, чтобы в структуре окна были дополнительные данные для экземпляра класса; установить универсальный WndProc для всех элементов управления положить ссылку на this
SetWindowLongPtr и т. д.)
С другой стороны, производный класс должен иметь возможность указывать стили и расширенные стили, имя класса для окна и т. Д.
Если создание экземпляра окна в конструкторе элемента управления является контрактом, который необходимо выполнить, я не вижу другого решения, кроме как использовать виртуальные машины в ctor (что не будет работать).
Возможные обходные пути:
Мне лично не нравится ни один из них. Из любопытства я проверил, как эта проблема решается в VCL Delphi, и вы знаете, что базовый класс вызывает CreateParams, который является виртуальным (Delphi допускает такие вызовы и гарантирует, что они безопасны — поля класса инициализируются равными 0 при создании ).
Как можно преодолеть это языковое ограничение?
Редактировать: В ответ на ответы:
Вызов CreateChild из производного конструктора. Вы уже требуете, чтобы CreateChild был определен, так что это пошаговый шаг. Вы можете добавить функцию protected: void init () в базовый класс, чтобы сохранить инкапсуляцию такой инициализации.
Это будет работать, но это не вариант — процитировать известный C ++ FAQ:
Первый вариант простейший изначально, хотя код, который на самом деле хочет создавать объекты, требует немного самодисциплины программиста, что на практике означает, что вы обречены. Серьезно, если есть только одно или два места, которые фактически создают объекты этой иерархии, самодисциплина программиста довольно локализована и не должна вызывать проблем.
Используйте CRTP. Сделайте базовый класс шаблоном, сделайте так, чтобы потомок поставил DerivedType, и вызовите конструктор таким образом. Такая конструкция иногда может полностью устранить виртуальные функции.
Это не вариант, потому что он будет работать только один раз для базового класса и его непосредственного потомка.
Сделайте дочерний указатель аргументом конструктора, например, для фабричной функции.
На данный момент это лучшее решение — внедрение кода в базовый ctor. В моем случае мне даже не нужно будет это делать, потому что я могу просто параметризовать базовый ctor и передавать значения от потомка. Но это на самом деле будет работать.
Используйте шаблон фабричной функции, чтобы сгенерировать дочерний указатель соответствующего типа перед возвратом родительского. Это устраняет сложность шаблона класса. Используйте шаблон признаков типа для группировки дочерних и родительских классов.
Да, но у него есть некоторые недостатки:
Вызов виртуальных методов в C ++ запрещен, однако в некоторых ситуациях это может быть очень полезно.
Нет, вызов виртуального метода в конструкторе отправляет самому производному завершенному объекту, который находится в стадии разработки. Это не запрещено, оно четко определено и делает единственное, что имеет смысл.
Базовому классу C ++ не разрешается знать идентичность самого производного объекта, к лучшему или к худшему. Согласно модели, производный объект не начинает существовать до тех пор, пока не будут запущены базовые конструкторы, поэтому нет ничего, что могло бы получить информацию о типе.
Некоторые альтернативы в вашем случае:
protected: void init()
функция в базовом классе, чтобы сохранить инкапсуляцию такой инициализации.Других решений пока нет …