プライベートな純粋仮想関数のポイントは何ですか?


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は長い間、プライベートバーチャルの使用を推奨していませんでした。

したがって、まず混乱を取り除くために:はい、プライベート仮想関数を派生クラスでオーバーライドできます。派生クラスのメソッドは、基本クラスから仮想関数を呼び出すことはできませんが、独自の実装を提供できます。Herb Sutterによると、基本クラスにパブリック非仮想インターフェイスがあり、派生クラスでカスタマイズできるプライベート実装があると、「インターフェイスの仕様と実装のカスタマイズ可能な動作の仕様の分離」が改善されます。詳細については、彼の記事「Virtuality」を参照してください。

しかし、私の意見では、あなたが提示したコードにはもう1つ興味深いことに注目すべきものがあります。パブリックインターフェイスは、オーバーロードされた非仮想関数のセットで構成され、それらの関数は非パブリックでオーバーロードされていない仮想関数を呼び出します。C ++の世界ではいつものように、それはイディオムであり、名前があり、もちろん便利です。その名は(サプライズ、サプライズ!)

「パブリックオーバーロードされた非バーチャルは、保護された非オーバーロードされたバーチャルを呼び出します」

非表示ルール適切に管理するのに役立ちます。詳しくはこちらをご覧ください。すぐに説明します。

Engineクラスの仮想関数もそのインターフェースであり、純粋な仮想ではないオーバーロードされた関数のセットであると想像してください。それらが純粋な仮想である場合、以下で説明するように、同じ問題が依然として発生する可能性がありますが、クラス階層の下位です。

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

次に、派生クラスを作成し、メソッドにのみ2つのintを引数として取る新しい実装を提供する必要があると仮定します。

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宣言を派生クラスに配置するのを忘れた場合(または2番目のオーバーロードを再定義した場合)、以下のシナリオで問題が発生する可能性があります。

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

Engineメンバーの非表示を防止しなかった場合、ステートメントは次のようになります。

myV8->SetState(5, true);

void SetState( int var, int val )派生クラスから呼び出し、に変換trueintます。

インターフェースが仮想ではなく、仮想実装が非公開である場合(例のように)、派生クラスの作成者は考える必要のある問題が1つ少なく、簡単に書くことができます。

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

なぜ仮想関数はプライベートでなければならないのですか?公開できますか?
リッチ

Herb Sutterが彼の「仮想性」の記事で示したガイドラインは、今日も当てはまるのでしょうか。
nurabha 2016年

@Rich可能ですが、非公開にすることで、彼らの意図をより明確に伝えることができます。まず、インターフェースをパブリックにし、実装を非パブリックにすることに固執した場合の懸念の分離を示しています。次に、継承するクラスで基本実装を呼び出せるようにしたい場合は、それらを保護されていると宣言できます。基本的なものを呼び出さずに独自の実装を提供するだけの場合は、それらをプライベートにします。
ダン・

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

純粋仮想 -派生クラスに実装を義務付けるだけです。

編集:これについての詳細:Wikipedia :: NVI-idiom


17

1つには、これにより、派生クラスが(純粋仮想関数宣言を含む)基本クラスが呼び出すことができる関数を実装できるようになります。


5
それだけで、基本クラスを呼び出すことができます!
underscore_d

4

編集:オーバーライドする機能とアクセス/呼び出す機能に関する説明を明確にしました。

これらのプライベート関数をオーバーライドすることができます。たとえば、次の不自然な例が機能します(編集:派生クラスメソッドをプライベートにし、派生クラスメソッドの呼び出しを削除して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:ユーザーはパブリック派生クラスの仮想メソッドオーバーライドにアクセスできますが、基本クラスのオーバーライドにはアクセスできません。この場合、派生クラスの作成者は、仮想メソッドのオーバーライドもプライベートに保つことができます。実際、私はそれを強調するために上記のサンプルコードを変更します。どちらの方法でも、常にパブリックに継承し、プライベート基本クラスの仮想メソッドをオーバーライドできますが、独自の派生クラスの仮想メソッドにしかアクセスできません。オーバーライドとアクセス/呼び出しの区別をしていることに注意してください。
ボイド

あなたが間違っているからです。クラス間の継承の可視性。オーバーライド(またはアクセス)できるものとできないものEngineDerivedEngineは何の関係もありませんDerivedEngine
wilhelmtell

@wilhelmtell:ため息 あなたはもちろん正しいです。それに応じて回答を更新します。
ボイド

3

プライベート仮想メソッドは、特定の関数をオーバーライドできる派生クラスの数を制限するために使用されます。プライベート仮想メソッドをオーバーライドする必要がある派生クラスは、基本クラスのフレンドでなければなりません。

DevX.comの簡単な説明があります。


編集プライベート仮想メソッドは、テンプレートメソッドパターンで効果的に使用されます。派生クラスはプライベート仮想メソッドをオーバーライドできますが、派生クラスはそのベースクラスのプライベート仮想メソッドを呼び出すことはできません(例では、SetStateBoolおよびSetStateInt)。基本クラスだけがそのプライベート仮想メソッドを効果的に呼び出すことができます(派生クラスが仮想関数の基本実装を呼び出す必要がある場合のみ、仮想関数を保護します)。

Virtualityに関する興味深い記事が見つかります。


2
@Gentleman ...コリンDベネットのコメントまで下にスクロールします。彼は「プライベート仮想関数は派生クラスによってオーバーライドできますが、基本クラス内からしか呼び出せない」と考えているようです。@Michael Goldshteynもそのように考えています。
BeeBand

プライベートベースのクラスは、その派生クラスからは見えないという原則を忘れていると思います。それがOOPルールであり、OOPであるすべての言語に適用されます。派生クラスがその基本クラスのプライベート仮想メソッドを実装するには、それがfriend基本クラスのである必要があります。Qtは、XML DOMドキュメントモデルを実装するときに同じアプローチを採用しました。
Buhake Sindi、

@紳士:いいえ、私は忘れていません。コメントにタイプミスをしました。「基本クラスのメソッドへのアクセス」の代わりに、「基本クラスのメソッドをオーバーライドできる」と書いておくべきでした。派生クラスは、その基本クラスメソッドにアクセスできない場合でも、確実にプライベート仮想基本クラスメソッドをオーバーライドできます。あなたが指摘したDevX.comの記事は正しくありません(パブリック継承)。私の答えのコードを試してください。プライベート仮想基本クラスメソッドにもかかわらず、派生クラスはそれをオーバーライドできます。プライベート仮想基本クラスメソッドをオーバーライドする機能と、それを呼び出す機能を混同しないでください。
2010年

@紳士:@wilhelmtellが私の回答/コメントのエラーを指摘しました。基本クラスメソッドの派生クラスのアクセシビリティに影響を与える継承についての私の主張は無効になりました。あなたの回答に対する問題のコメントを削除しました。
2010年

@Void、派生クラスその基本クラスのプライベート仮想メソッドをオーバーライドできますが、使用できません。したがって、これは基本的にテンプレートメソッドのパターンです。
Buhake Sindi、

0

TL; DR回答:

それをカプセル化の別のレベルのように扱うことができます- 保護プライベートの間のどこか:子クラスから呼び出すことはできませんが、オーバーライドすることはできます。

テンプレートメソッドのデザインパターンを実装するときに役立ちます。protectedを使用することもできますが、pカプセル化が優れているため、privatevirtualを併用することをお勧めします。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.