Когда вызывать функцию верхней иерархии в переопределенной функции? До или после другого кода?

Новичок в C++, так что терпите меня.

Когда у вас есть виртуальная переопределенная функция, когда вы должны вызывать базовую функцию? Должно ли это быть так:

void Player::onCollision ( Collidable& otherObject )
{
    /* OTHER CODE */
    /* OTHER CODE */

    PhysicalActor::onCollision ( otherObject );
}

Или это:

void Player::onCollision ( Collidable& otherObject )
{
    PhysicalActor::onCollision ( otherObject );

    /* OTHER CODE */
    /* OTHER CODE */
}

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

Извините, если это простой вопрос!

Спасибо!

1 ответ

В языке нет такого требования. Это вопрос проектирования интерфейса, и хотя библиотекам иногда требуется вызывать базовый класс в определенное время, я считаю, что API плохой.

Лучшие проекты не требуют от пользователя называть базовую версию... когда-либо. Это устраняет любую нагрузку на автора производного класса. Я слышал об этом так называемом дизайне API без контракта, так как нет никакого соглашения, которое должно быть принято, чтобы переопределить функцию.

Как правило, с такими контрактами, которые вы заставляете своих пользователей принимать, это смешивает логику базового класса с частью, которую клиент переопределяет. Так что вместо:

class Base {
//...   
public:

   // ** this version must be called before an override does anything
   virtual void doSomething() {
      prepare(data); 
   }

private:
   Data data;
}

Для этого необходимо, чтобы при переопределении doSomething вы сначала вызывали базовую версию, прежде чем что-либо делать.

Лучший интерфейс будет выглядеть примерно так:

class Base {
//...   
public:
   void doSomething() {  // ** NOT virtual
      prepare(data);
      doSomethingHook();
   }
protected:
   virtual void doSomethingHook() { } // default is to do nothing

private:
   Data data;
}

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

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

Другие вопросы по тегам