Какова точка частной чистой виртуальной функции?

Я встретил следующий код в файле заголовка:

class Engine
{
public:
    void SetState( int var, bool val );
    {   SetStateBool( int var, bool val ); }

    void SetState( int var, int val );
    {   SetStateInt( int var, int val ); }
private:
    virtual void SetStateBool(int var, bool val ) = 0;    
    virtual void SetStateInt(int var, int val ) = 0;    
};

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

Ответ 1

Вопрос в теме предполагает довольно частое замешательство. Путаница достаточно распространена, что С++ часто задаваемые вопросы защищали от использования частных виртуальных машин в течение длительного времени, потому что путаница, казалось, была плохой.

Итак, чтобы сначала избавиться от путаницы: да, частные производные функции могут быть переопределены в производных классах. Методы производных классов не могут вызывать виртуальные функции из базового класса, но они могут обеспечить им собственную реализацию. Согласно Herb Sutter, наличие публичного не виртуального интерфейса в базовом классе и частная реализация, которые могут быть настроены в производных классах, позволяет лучше "разделять спецификацию интерфейса от спецификации реализации, настраиваемого поведением". Подробнее об этом читайте в своей статье "Virtuality" .

В представленном вами коде есть еще одна интересная вещь, которая, на мой взгляд, заслуживает большего внимания. Открытый интерфейс состоит из набора перегруженных не виртуальных функций, и эти функции вызывают непубличные, не перегруженные виртуальные функции. Как обычно в мире С++ это идиома, у нее есть имя и, конечно, полезно. Имя (сюрприз, сюрприз!)

"Общественные перегруженные не виртуальные вызовы защищают неперегруженные виртуальные машины"

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

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

class Engine
{
public:
    virtual void SetState( int var, bool val ) {/*some implementation*/}
    virtual void SetState( int var, int val )  {/*some implementation*/}
};

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

class MyTurbochargedV8 : public Engine
{
public:
    // To prevent SetState( int var, bool val ) from the base class,
    // from being hidden by the new implementation of the other overload (below),
    // you have to put using declaration in the derived class
    using Engine::SetState;

    void SetState( int var, int val )  {/*new implementation*/}
};

Если вы забыли поместить декларацию using в производном классе (или переопределить вторую перегрузку), вы можете столкнуться с проблемами в приведенном ниже сценарии.

MyTurbochargedV8* myV8 = new MyTurbochargedV8();
myV8->SetState(5, true);

Если вы не предотвратили скрытие членов Engine, утверждение:

myV8->SetState(5, true);

будет вызывать void SetState( int var, int val ) из производного класса, преобразовывая true в int.

Если интерфейс не является виртуальным и виртуальная реализация не является общедоступной, как в вашем exmaple, у автора производного класса есть одна проблема, о которой стоит подумать, и может просто написать

class MyTurbochargedV8 : public Engine
{
private:
    void SetStateInt(int var, int val )  {/*new implementation*/}
};

Ответ 2

Частная чистая виртуальная функция является базой не виртуального интерфейса идиомы (ОК, это не совсем всегда чисто виртуальное, но все же виртуальное там). Конечно, это используется и для других вещей, но я считаю это наиболее полезным (: двумя словами: в публичной функции вы могли бы поместить некоторые общие вещи (например, журнал, статистику и т.д.) В начале и в конце функции, а затем "в середине" для вызова этой частной виртуальной функции, которая будет отличаться для конкретного производного класса. Что-то вроде:

class Base
{
    // ..
public:
    void f();
private:
    virtual void DerivedClassSpecific() = 0;
   // ..
};
void Base::f()
{
    //.. Do some common stuff
    DerivedClassSpecific();
    //.. Some other common stuff
}
// ..

class Derived: public Base
{
    // ..
private:
    virtual void DerivedClassSpecific();
    //..
};
void Derived::DerivedClassSpecific()
{
    // ..
}

Чистый виртуальный - просто обязывает производные классы реализовать его.

EDIT: Подробнее об этом: Wikipedia:: NVI-idiom

Ответ 3

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

Ответ 4

EDIT: Уточненные утверждения о возможности переопределения и возможности доступа/вызова.

Он сможет переопределить эти частные функции. Например, работает следующий надуманный пример (EDIT: сделанный метод производного класса private, и удалите вызов метода производного класса в main(), чтобы лучше продемонстрировать намерение используемого шаблона проектирования.):

#include <iostream>

class Engine
{
public:
  void SetState( int var, bool val )
  {
    SetStateBool( var, val );
  }

  void SetState( int var, int val )
  {
    SetStateInt( var, val );
  }

private:

    virtual void SetStateBool(int var, bool val ) = 0;
    virtual void SetStateInt(int var, int val ) = 0;

};

class DerivedEngine : public Engine
{
private:
  virtual void SetStateBool(int var, bool val )
  {
    std::cout << "DerivedEngine::SetStateBool() called" << std::endl;
  }

  virtual void SetStateInt(int var, int val )
  {
    std::cout << "DerivedEngine::SetStateInt() called" << std::endl;
  }
};


int main()
{
  DerivedEngine e;
  Engine * be = &e;

  be->SetState(4, true);
  be->SetState(2, 1000);
}

Private virtual методы в базовом классе, подобные тем, которые содержатся в вашем коде, обычно используются для реализации шаблона шаблона шаблона. Этот шаблон проектирования позволяет изменить поведение алгоритма в базовом классе без изменения кода в базовом классе. Вышеприведенный код, в котором методы базового класса вызывается с помощью указателя базового класса, является простым примером шаблона метода шаблона.

Ответ 5

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

Краткое описание можно найти DevX.com.


EDIT Частный виртуальный метод эффективно используется в Шаблон метода шаблона. Производные классы могут переопределять частный виртуальный метод, но производные классы не могут называть его виртуальным методом класса private (в вашем примере SetStateBool и SetStateInt). Только базовый класс может эффективно вызывать свой частный виртуальный метод (Только если производным классам необходимо вызвать базовую реализацию виртуальной функции, сделать виртуальную функцию защищенной).

Интересная статья о Virtuality.

Ответ 6

TL; DR ответ:

Вы можете рассматривать это как другой уровень инкапсуляции - где-то между protected и private: вы не можете вызвать его из дочернего класса, но вы можете переопределить его.

Это полезно при реализации шаблона проектирования Template Method. Вы можете использовать protected, но private вместе с virtual можно рассматривать как лучший выбор из-за лучшей инкапсуляции.