Переопределение общедоступных виртуальных функций частными функциями в C ++ - PullRequest
45 голосов
/ 27 января 2009

Есть ли какая-либо причина отличать разрешения для переопределенной виртуальной функции C ++ от базового класса? Есть ли опасность при этом?

Например:

class base {
    public:
        virtual int foo(double) = 0;
}

class child : public base {
    private:
        virtual int foo(double);
}

C ++ faq говорит, что это плохая идея, но не говорит почему.

Я видел эту идиому в некотором коде, и я считаю, что автор пытался сделать класс окончательным, исходя из предположения, что невозможно переопределить функцию закрытого члена. Тем не менее, В этой статье показан пример переопределения частных функций. Конечно, другая часть C ++ faq рекомендует не делать этого.

Мои конкретные вопросы:

  1. Есть ли техническая проблема с использованием другого разрешения для виртуальных методов в производных классах по сравнению с базовым классом?

  2. Существует ли законная причина для этого?

Ответы [ 7 ]

39 голосов
/ 27 января 2009

Вы получаете удивительный результат, что если у вас есть ребенок, вы не можете вызвать foo, но вы можете привести его к базе, а затем вызвать foo.

child *c = new child();
c->foo; // compile error (can't access private member)
static_cast<base *>(c)->foo(); // this is fine, but still calls the implementation in child

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

30 голосов
/ 27 января 2009

Проблема в том, что методы Базового класса являются его способом объявления его интерфейса. По сути, он говорит: «Это то, что вы можете сделать с объектами этого класса».

Когда в производном классе вы делаете что-то, что База объявила как публичное частное, вы забираете что-то. Теперь, даже несмотря на то, что производный объект "is-a" базовый объект, то, что вы должны делать с объектом базового класса, вы не можете делать с объектом производного класса, ломая Liskov Substitution Prinicple

Не вызовет ли это "техническую" проблему в вашей программе? Возможно, нет. Но это, вероятно, будет означать, что объекты ваших классов не будут вести себя так, как ожидают пользователи.

Если вы окажетесь в ситуации, когда это именно то, что вам нужно (за исключением случаев, когда устаревший метод упоминается в другом ответе), скорее всего, у вас есть модель наследования, где наследование на самом деле не моделируется »is-a , "(например, пример Скотта Майерса Square, унаследованный от Rectangle, но вы не можете изменить ширину квадрата независимо от его высоты, как вы можете для прямоугольника), и вам может потребоваться пересмотреть ваши классовые отношения.

6 голосов
/ 27 января 2009

Технических проблем нет, но в итоге вы столкнетесь с ситуацией, когда общедоступные функции будут зависеть от того, есть ли у вас базовый или производный указатель.

Это, на мой взгляд, было бы странно и запутанно.

4 голосов
/ 27 января 2009

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

3 голосов
/ 27 января 2009

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

1 голос
/ 05 января 2017

Хорошим вариантом использования частного наследования является интерфейс событий Listener / Observer.

Пример кода для частного объекта:

class AnimatableListener {
  public:
    virtual void Animate(float delta_time);
};

class BouncyBall : public AnimatableListener {
  public:
    void TossUp() {}
  private:
    void Animate(float delta_time) override { }
};

Некоторые пользователи объекта хотят иметь родительские функции, а некоторые - дочерние:

class AnimationList {
   public:
     void AnimateAll() {
       for (auto & animatable : animatables) {
         // Uses the parent functionality.
         animatable->Animate();
       }
     }
   private:
     vector<AnimatableListener*> animatables;
};

class Seal {
  public:
    void Dance() {
      // Uses unique functionality.
      ball->TossUp();
    }
  private:
    BouncyBall* ball;
};

Таким образом, AnimationList может содержать ссылку на родителей и использовать родительские функции. В то время как Seal содержит ссылку на дочерний элемент и использует уникальные дочерние функции и игнорирует родительские функции. В этом примере Seal не должен вызывать Animate. Теперь, как упомянуто выше, Animate можно вызвать путем приведения к базовому объекту, но это сложнее и, как правило, не следует делать.

1 голос
/ 27 января 2009
  1. Никаких технических проблем, если вы имеете в виду под техническими, поскольку существует скрытая стоимость выполнения.
  2. Если вы наследуете базу публично, вы не должны этого делать. Если вы наследуете с помощью защищенного или частного, это может помочь предотвратить использование методов, которые не имеют смысла, если у вас нет базового указателя.
...