В чем смысл частной чисто виртуальной функции? - PullRequest
129 голосов
/ 19 октября 2010

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

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 ]

196 голосов
/ 20 октября 2010

Вопрос в теме наводит на мысль о довольно распространенной путанице.Путаница достаточно распространена, и 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*/}
};
40 голосов
/ 19 октября 2010

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

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-идиома

17 голосов
/ 19 октября 2010

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

4 голосов
/ 19 октября 2010

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

Он сможет переопределить эти частные функции. Например, работает следующий надуманный пример ( EDIT: сделал метод производного класса закрытым и удалил вызов метода производного класса в 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 в базовом классе, подобные тем, которые используются в вашем коде, обычно используются для реализации шаблона проектирования Template Method . Этот шаблон проектирования позволяет изменить поведение алгоритма в базовом классе, не изменяя код в базовом классе. Приведенный выше код, в котором методы базового класса вызываются через указатель базового класса, является простым примером шаблона Template Method.

2 голосов
/ 19 октября 2010

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

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

* 1006.*

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

Интересная статья можетможно найти около Виртуальность .

...