#c #polymorphism
#c #полиморфизм
Вопрос:
Я пытаюсь решить проблему, когда у меня есть несколько классов, в которых мне нужно выполнить некоторую общую работу, а затем кучу специфичной для проблемы работы, и когда это будет закончено, выполните еще некоторую обработку, общую для всех этих классов.
У меня есть базовый и производный классы, которые оба имеют функцию с именем Execute. Когда я вызываю производную версию этой функции, я хотел бы иметь возможность выполнить некоторую обработку, общую для всех моих производных классов в базе, а затем продолжить выполнение в моем Derived::Execute и вернуться к Base::Execute, чтобы завершить некоторую общую работу.
Возможно ли это в C и как лучше всего это сделать?
В этом и заключается идея, однако, вероятно, это не очень выполнимо подобным образом:
class Base
{
public:
virtual void Execute();
};
Base::Execute() {
// do some pre work
Derived::Execute(); //Possible????
// do some more common work...
}
class Derived : public Base
{
public:
void Execute();
};
void Derived::Execute()
{
Base::Execute();
//Do some derived specific work...
}
int main()
{
Base * b = new Derived();
b.Execute(); //Call derived, to call into base and back into derived then back into base
}
Комментарии:
1. Ошибка .. это приведет к переполнению стека?
2. @Nim: вот почему я прошу совета…
Ответ №1:
Используйте чисто виртуальную функцию из base..
class Base
{
public:
void Execute();
private:
virtual void _exec() = 0;
};
Base::Execute() {
// do some common pre work
// do derived specific work
_exec();
// do some more common work...
}
class Derived : public Base
{
private:
void _exec() {
// do stuff
}
};
int main()
{
Base * b = new Derived();
b.Execute();
}
РЕДАКТИРОВАТЬ: немного изменил поток после еще одного прочтения вопроса .. 🙂 Приведенный выше механизм должен в точности соответствовать тому, что вам требуется сейчас —
т. е.
- Базовые общие вещи
- Производный специфический материал
- Снова базовые общие вещи
Комментарии:
1. И, конечно, в зависимости от ваших требований у вас всегда есть варианты (1) сделать
_exec
непорочную реализацию, например, с помощью ничего не делающей реализации, если это даст разумный результат; и / или (2) сделатьExecute
виртуальную, чтобы производным классам не приходилось следовать шаблону, если они думают, что у них лучшая реализация.
Ответ №2:
В C это называется идиомой NVI (невиртуальный интерфейс, от Херба Саттера здесь) и в основном гласит, что у вас не должно быть общедоступных виртуальных функций, а скорее защищенных / частных виртуальных функций. Пользовательский код должен будет вызвать вашу общедоступную невиртуальную функцию в базовом классе, и это приведет к передаче в защищенный / частный виртуальный метод.
С точки зрения дизайна обоснование заключается в том, что базовый класс имеет два разных интерфейса: с одной стороны, пользовательский интерфейс, определяемый общедоступным подмножеством класса, а с другой — интерфейс расширяемости или то, как класс может быть расширен. Используя NVI, вы разъединяете оба интерфейса и предоставляете больший контроль в базовом классе.
class base {
virtual void _foo(); // interface to extensions
public:
void foo() { // interface to users
// do some ops
_foo();
}
};
Комментарии:
1. В C (и на других языках) это называется шаблоном шаблонного метода. В идиоме NVI функции невиртуального базового класса фактически ничего не делают.
2. @James Kanze: Ну, я бы не знал, но Херб Саттер, который создал термин NVI в статье, на которую я ссылался, называет его NVI, и в его примере невиртуальные функции фактически реализуют функциональность в терминах защищенных виртуальных функций. Бегло взгляните на связанную статью.
3. @David Rodriguez Мне следовало быть более точным. Решение, которое ищет исходный плакат, — это шаблон метода template, а не NVI. Ваш пример — NVI. Разница в том, что в шаблоне шаблонного метода базовый класс выполняет некоторую реальную работу; он, так сказать, «пилотирует» алгоритм и вызывает виртуальные функции только для определенных настраиваемых действий. В NVI невиртуальные общедоступные функции фактически ничего не делают за пределами проверки (например, до и после выполнения условий).
4. @Джеймс Канзе: Вы читали статью, на которую дана ссылка? Общедоступный интерфейс в примере, который предлагает Саттер, является
int Process( Gadgetamp; )
, а внутренний виртуальный интерфейс предлагаетvirtual int DoProcessPhase1( Gadgetamp; );
иvirtual int DoProcessPhase2( Gadgetamp; );
: Невиртуальная функция управляет алгоритмом и извлекает определенные модели поведения из производных классов. Обратите внимание, что NVI, представленный Саттером, отличается от NVI, описанного в Википедии. По сути, это более строгий TM, поскольку он требует, чтобы виртуальные методы были непубличными. Но это только детали 🙂5. @David Да, я прочитал статью. Я также прочитал это, когда Херб впервые опубликовал это. Херб перепутал это с шаблоном метода template, когда он впервые опубликовал его. Вы могли бы взглянуть на «Последующее примечание», которое он добавил; он придумал название NVI, потому что я указал на различия между тем, что он представлял, и шаблоном шаблонного метода. Он больше не называет это шаблоном шаблонного метода, потому что он отличается.
Ответ №3:
Переверните проблему с головы на ноги. Что вы на самом деле хотите иметь, так это алгоритм базового класса, к которому могут подключаться производные классы:
class Base {
public:
void Execute()
{
// do something
execute();
// do some more things
}
private:
virtual void execute() = 0;
};
class Derived : public Base {
public:
// whatever
private:
virtual void execute()
{
//do some fancy stuff
}
};
Разрешение производным классам подключаться к алгоритмам базового класса часто называется шаблоном «шаблонный метод» (который не имеет ничего общего с template
. Отсутствие общедоступных виртуальных функций в интерфейсе базового класса часто называют шаблоном «невиртуальный интерфейс».
Я уверен, что Google может многое найти для вас по этим двум.
Комментарии:
1. «Отсутствие виртуальных функций в интерфейсе базового класса» часто называют «не интерфейсом»:-). (Я уверен, что вы имели в виду отсутствие общедоступных виртуальных функций, а не виртуальных функций вообще.) Обратите внимание, что существует разница между шаблоном шаблонного метода и шаблоном невиртуального интерфейса; в шаблоне шаблонного метода базовый класс — это нечто большее, чем просто интерфейс.
2. @James: Я хотел ответить, что частные функции не являются частью интерфейса класса, но, во-первых, существуют также защищенные члены, и они являются частью интерфейса для производных классов, а также частные виртуальные функции переопределяются производными классами, поэтому являются ли они частью интерфейса класса, несколько спорно. Поэтому вместо этого я изменю свой ответ. Спасибо, что указали на это.
Ответ №4:
Переместите это Base::Execute
внутрь в две функции, а затем используйте RAII для простой реализации.
class Base{
protected:
void PreExecute(){
// stuff before Derived::Execute
}
void PostExecute(){
// stuff after Derived::Execute
}
public:
virtual void Execute() = 0;
};
struct ScopedBaseExecute{
typedef void(Base::*base_func)();
ScopedBaseExecute(Base* p)
: ptr_(p)
{ ptr_->PreExecute() }
~ScopedBaseExecute()
{ ptr_->PostExecute(); }
Base* ptr_;
};
class Derived : public Base{
public:
void Execute{
ScopedBaseExecute exec(this);
// do whatever you want...
}
};
Комментарии:
1. Это при условии, что имеет смысл запускать
PostExecute
, когда «делай все, что хочешь» выдает исключение.2. @Steve: Допустимая точка зрения. Я думаю, что в C 0x мы получаем
current_exception
функцию, и если вы не хотите, чтобы что-то выполнялось при возникновении исключения, вы можете протестировать это, я думаю. Или создайте 2 класса RAII.