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

Я наткнулся на следующий код в заголовочном файле:

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 класс или класс, производный от него, должен обеспечивать реализацию этих чисто виртуальных функций. Но я не думал, что производные классы могут иметь доступ к этим частным функциям, чтобы переопределить их - так зачем делать их виртуальными?

5 ответов

Решение

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

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

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

"Не перегруженные общедоступные не виртуальные вызовы защищенные неперегруженные виртуалы"

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

Представьте себе, что виртуальные функции 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,

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

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

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

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()
{
    // ..
}

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

РЕДАКТИРОВАТЬ: Подробнее об этом: Википедия::NVI-идиома

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

РЕДАКТИРОВАТЬ: Уточненные заявления о возможности переопределения и возможности доступа / вызова.

Он сможет переопределить эти частные функции. Например, работает следующий надуманный пример (РЕДАКТИРОВАТЬ: сделать метод производного класса частным и удалить вызов метода производного класса в 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);
}

Privatevirtual методы в базовом классе, подобные тем, что в вашем коде, обычно используются для реализации шаблона проектирования Template Method. Этот шаблон проектирования позволяет изменить поведение алгоритма в базовом классе, не изменяя код в базовом классе. Приведенный выше код, в котором методы базового класса вызываются через указатель базового класса, является простым примером шаблона Template Method.

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

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


РЕДАКТИРОВАТЬ Закрытый виртуальный метод эффективно используется в шаблонном шаблоне. Производные классы могут переопределять закрытый виртуальный метод, но производные классы не могут вызывать его частный виртуальный метод базового класса (в вашем примере SetStateBool а также SetStateInt). Только базовый класс может эффективно вызывать свой частный виртуальный метод (только если производные классы должны вызывать базовую реализацию виртуальной функции, сделайте виртуальную функцию защищенной).

Интересную статью можно найти о виртуальности.

TL;DR ответ:

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

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

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