У чому сенс приватної чистої віртуальної функції?


139

У файлі заголовка я натрапив на такий код:

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

Відповіді:


209

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

Тож, щоб спочатку позбутися від плутанини: Так, приватні віртуальні функції можна переоминути у похідних класах. Методи похідних класів не можуть викликати віртуальні функції з базового класу, але вони можуть забезпечити їх власну реалізацію. За словами Герба Саттера, наявність публічного невіртуального інтерфейсу в базовому класі та приватна реалізація, яку можна налаштувати у похідних класах, дозволяє краще "відокремити специфікацію інтерфейсу від специфікації налаштованої поведінки реалізації". Детальніше про це ви можете прочитати в його статті "Віртуальність" .

Однак у кодексі, який ви подали, є ще одна цікава річ, яка, на мою думку, заслуговує на дещо більше уваги. Загальнодоступний інтерфейс складається з набору перевантажених невіртуальних функцій, і ці функції називають непублічними, не перевантаженими віртуальними функціями. Як зазвичай у світі С ++, це ідіома, вона має назву і, звичайно, корисна. Назва (сюрприз, сюрприз!)

"Загальнодоступні перевантажені невіртуальні виклики, захищені неперевантаженими віртуалами"

Це допомагає правильно керувати правилом приховування . Ви можете прочитати більше про це тут , але я спробую пояснити це найближчим часом.

Уявіть, що віртуальні функції 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*/}
};

Якщо ви забули поставити використовуючу декларацію у похідний клас (або перевизначити другу перевантаження), ви можете зіткнутися з проблемою у нижченаведеному сценарії.

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*/}
};

чому віртуальна функція повинна бути приватною? Чи може це бути публічним?
Багатий

Цікаво, чи дотримуються сьогодні настанови, які дав Герб Саттер у своїй статті "Віртуальність"?
нурабха

@Rich Ви могли, але зробивши їх непублічними, ви можете передати їх наміри більш чітко. По-перше, це демонструє розділення проблем, якщо ви дотримуєтесь того, щоб зробити інтерфейс загальнодоступним та впровадження непублічним. По-друге, якщо ви хочете, щоб у спадок класи могли викликати базові реалізації, ви можете оголосити їх захищеними; якщо ви хочете, щоб вони надавали власні реалізації без виклику в базові, ви робите їх приватними.
Dan

43

Приватна чиста віртуальна функція є базою інтерфейсу невіртуальну ідіом (OK, це не абсолютно завжди чисто віртуальними, але все - таки віртуальна там). Звичайно, це використовується і для інших речей, але я вважаю це найбільш корисним (: Двома словами: на загальнодоступній функції ви можете помістити деякі звичайні речі (наприклад, ведення журналів, статистику тощо) на початку та в кінці функції, а потім "посередині" викликати цю приватну віртуальну функцію, яка буде відрізнятися для конкретного похідного класу.

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


17

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


5
що тільки базовий клас може дзвонити!
підкреслюй_d

4

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

Він зможе змінити ці приватні функції. Наприклад, наступний надуманий приклад працює ( 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методи базового класу, такі, як у вашому коді, зазвичай використовуються для реалізації шаблону дизайну методу шаблону . Цей шаблон дизайну дозволяє змінювати поведінку алгоритму в базовому класі без зміни коду в базовому класі. Вищевказаний код, де методи базового класу викликаються через вказівник базового класу, є простим прикладом шаблону методу шаблону.


Я розумію, але якщо похідні класи все одно мають такий собі доступ, навіщо турбуватися робити їх приватними?
BeeBand

@BeeBand: Користувач матиме доступ до віртуального методу відкритого загальнодоступного класу, але не матиме доступу до базового класу. Автор похідного класу в цьому випадку може також зберігати приватні зміни віртуального методу. Насправді я внесу зміни в зразок коду вище, щоб наголосити на цьому. Так чи інакше, вони завжди можуть успадковувати публічно і змінювати віртуальні методи приватного базового класу, але вони все ще мають лише доступ до власних похідних віртуальних методів класу. Зауважте, що я роблю різницю між переопрацюванням та доступом / викликом.
Недійсний

тому що ви помиляєтесь видимість успадковування між класами Engineі DerivedEngineне має нічого спільного з тим, що DerivedEngineможе або не може перекрити (або отримати доступ до цього питання).
wilhelmtell

@wilhelmtell: зітхання Ви, звичайно, правильні. Відповідно оновлю відповідь.
Недійсний

3

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

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


EDIT У шаблоні методу шаблону ефективно використовується приватний віртуальний метод . Похідні класи можуть змінювати приватний віртуальний метод, але похідні класи не можуть називати його базовим класом приватним віртуальним методом (у вашому прикладі SetStateBoolта SetStateInt). Тільки базовий клас може ефективно викликати його приватний віртуальний метод ( Тільки якщо похідні класи потребують виклику базової реалізації віртуальної функції, зробіть віртуальну функцію захищеною ).

Цікаву статтю можна знайти про віртуальність .


2
@ Джентльмен ... хммм прокрутіть до коментаря Коліна Д Беннета. Він, здається, вважає, що "Приватну віртуальну функцію можна замінити похідними класами, але її можна викликати лише з базового класу." @Michael Goldshteyn теж думає так.
BeeBand

Я думаю, ви забули принцип того, що приватний клас не може бачити його похідний клас. Це правила OOP, і вони застосовуються до всіх мов, які є OOP. Для того, щоб похідний клас реалізував приватний віртуальний метод свого базового класу, він повинен бути friendбазовим класом. Qt застосували той самий підхід, коли реалізували свою XML DOM модель документа.
Buhake Sindi

@ Джентльмен: Ні, я не забув. Я зробив друкарську помилку у своєму коментарі. Замість "доступу до методів базового класу" я повинен був написати "може замінити методи базового класу". Похідний клас, безумовно, може замінити приватний метод віртуальної базового класу, навіть якщо він не може отримати доступ до цього методу базового класу. Стаття DevX.com, яку ви вказали, була неправильною (публічна спадщина). Спробуйте код у моїй відповіді. Незважаючи на приватний метод віртуального базового класу, похідний клас здатний його перекрити. Не будемо плутати можливість переопределення приватного методу віртуального базового класу із можливістю викликати його.
Недійсний

@Gentleman: @wilhelmtell вказав на помилку у моїй відповіді / коментарі. Моя претензія щодо спадкування, що впливає на доступність похідних класів методу базового класу, була відхилена. Я видалив ваші відповіді образливих коментарів.
Недійсний

@Void, я бачу, що похідний клас може змінити приватний віртуальний метод базового класу, але він не може ним користуватися. Отже, це по суті шаблон шаблону методу.
Buhake Sindi

0

TL; DR відповідь:

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

Це корисно при реалізації шаблону дизайну Методу шаблону. Ви можете використовувати захищені , але приватне разом із віртуальним можна вважати кращим вибором через кращу інкапсуляцію.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.