Выполнение базовой функции перед продолжением в производной функции

#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. Базовые общие вещи
  2. Производный специфический материал
  3. Снова базовые общие вещи

Комментарии:

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.