C ++のプライベート仮想メソッド


125

C ++でプライベートメソッドを仮想化する利点は何ですか?

私はオープンソースのC ++プロジェクトでこれに気づきました:

class HTMLDocument : public Document, public CachedResourceClient {
private:
    virtual bool childAllowed(Node*);
    virtual PassRefPtr<Element> createElement(const AtomicString& tagName, ExceptionCode&);
};

9
問題は逆だと思います。何かを仮想化する理由は常に同じです。派生クラスがそれをオーバーライドできるようにするためです。だから問題は:仮想メソッドをプライベートにすることの利点は何ですか?その答えは、デフォルトですべてを非公開にすることです。:-)
ShreevatsaR 2014

1
@ShreevatsaRしかし、あなたはあなた自身の質問に答えさえしませんでした......
スペンサー

@ShreevatsaR私はあなたが別の方法で後方を意味すると思った:仮想メソッド非公開にすることの利点は何ですか?
ピーター-モニカを

回答:


115

ハーブ・サッターはここでそれを非常にうまく説明しています

ガイドライン#2:仮想関数をプライベートにすることをお勧めします。

これにより、派生クラスが関数をオーバーライドして、必要に応じて動作をカスタマイズできます。仮想関数を派生クラスから呼び出し可能にして直接公開する必要はありません(関数を保護しただけの場合は可能です)。ポイントは、カスタマイズを可能にする仮想関数が存在することです。また、派生クラスのコード内から直接呼び出す必要がない限り、それらを非公開にする必要はありません。


私の答えから推測できるかもしれませんが、私はSutterのガイドライン#3がガイドライン#2をウィンドウの外に押しやると思います。
Spencer

66

メソッドが仮想の場合、プライベートであっても派生クラスでオーバーライドできます。仮想メソッドが呼び出されると、オーバーライドされたバージョンが呼び出されます。

(Prasoon Sauravが彼の回答で引用したHerb Sutterとは対照的に、C ++ FAQ Lite 、ほとんどの場合人々を混乱させるため、プライベートバーチャルを推奨しません。)


41
C ++よくある質問Liteは以来、その勧告を変更したことが表示されます:「C ++ FAQは、以前は保護さvirtualsをではなく、プライベートvirtualsをを使用してお勧めしかし、民間の仮想的なアプローチは、今初心者の混乱が懸念小さいことが一般的に十分である。。
ザック・ザ・人間

19
しかし、専門家の混乱は依然として懸念事項です。私の隣に座っている4人のC ++専門家の誰も、プライベートバーチャルを意識していませんでした。
Newtonx

12

仮想メンバーを非公開と宣言するすべての呼びかけにもかかわらず、この議論は単純に水を保持しません。多くの場合、仮想関数の派生クラスのオーバーライドは、基本クラスバージョンを呼び出す必要があります。宣言されている場合はできませんprivate

class Base
{
 private:

 int m_data;

 virtual void cleanup() { /*do something*/ }

 protected:
 Base(int idata): m_data (idata) {}

 public:

 int data() const { return m_data; }
 void set_data (int ndata) { m_data = ndata; cleanup(); }
};

class Derived: public Base
{
 private:
 void cleanup() override
 {
  // do other stuff
  Base::cleanup(); // nope, can't do it
 }
 public:
 Derived (int idata): base(idata) {}
};

基本クラスのメソッドを宣言する必要がありprotectedます。

次に、メソッドをオーバーライドする必要があるが、呼び出さないことをコメントで示すという醜い方法をとる必要があります。

class Base
{
 ...
 protected:
 // chained virtual function!
 // call in your derived version but nowhere else.
 // Use set_data instead
 virtual void cleanup() { /* do something */ }
 ...

したがって、ハーブサッターのガイドライン#3 ...しかし、馬はとにかく納屋から出ています。

protected派生クラスの作成者が保護された内部構造を理解して適切に使用することを暗黙的に信頼しているものを宣言する場合、friend宣言がprivateメンバーに対するより深い信頼を示唆する方法と同じです。

その信頼に違反することで悪い振る舞いをするユーザー(たとえば、ドキュメントを読まないようにして「無知」のラベルが付けられている)は、自分のせいにしかできません。

更新:私は、プライベート仮想関数を使用して、この方法で仮想関数の実装を「チェーン」できると主張するフィードバックがありました。もしそうなら、是非見たいと思います。

私が使用しているC ++コンパイラでは、派生クラスの実装でプライベートベースクラスの実装を呼び出せません。

C ++委員会がこの特定のアクセスを許可するために「プライベート」を緩和した場合、私はすべてプライベート仮想関数を使用することになります。現状では、馬が盗まれた後も納屋のドアをロックすることをお勧めします。


3
あなたの主張は無効だと思います。APIの開発者は、誤って使用するのが難しいインターフェースを目指し、他の開発者が自分のミスを犯してはならないように努める必要があります。あなたの例でしたいことは、プライベート仮想メソッドだけで実装できます。
シギー、

1
それは私が言っていたものではありません。ただし、コードを再構築して、プライベートベースクラス関数を呼び出す必要なしに同じ効果を実現できます
sigy

3
あなたの例では、の動作を拡張したいとしますset_data。取扱説明書m_data = ndata;とは、cleanup();したがって、すべての実装のために保持しなければならない不変と考えられます。したがって、cleanup()非仮想およびプライベートにします。仮想でありクラスの拡張ポイントである別のプライベートメソッドへの呼び出しを追加します。これで、派生クラスがbaseを呼び出す必要がなくなりcleanup()、コードはクリーンなままになり、インターフェイスが誤って使用されにくくなります。
シギー、

2
@sigyゴールポストを動かすだけです。あなたはミミナルの例を超えて見る必要があります。cleanup()チェーン内のすべてのを呼び出す必要がある子孫がさらにある場合、引数はバラバラになります。または、チェーン内の各子孫に追加の仮想関数を推奨していますか?いや。ハーブサッターでさえ、保護された仮想機能をガイドライン#3の抜け穴として許可しました。とにかく、実際のコードがなければ、私を納得させることはできません。
スペンサー

2
次に、同意しないことに同意します;)
sigy '18年

9

Scott Meyersの「Effective C ++」の項目35:仮想関数の代替案を検討するときに、この概念に最初に遭遇しました興味があるかもしれない他の人のためにスコット・メイヤーズを参照したかった。

これは、非仮想インターフェイスのイディオムを介しテンプレートメソッドパターンの一部です。公開されているメソッドは仮想ではありません。むしろ、プライベートな仮想メソッド呼び出しをラップします。その後、基本クラスはプライベート仮想関数呼び出しの前後にロジックを実行できます。

public:
  void NonVirtualCalc(...)
  {
    // Setup
    PrivateVirtualCalcCall(...);
    // Clean up
  }

これは非常に興味深いデザインパターンだと思います。追加したコントロールがどのように役立つかは、きっとわかると思います。

  • なぜ仮想関数を作るのprivateですか?一番の理由は、すでにpublic対面方式を提供していることです。
  • protected他の興味深いものにメソッドを使用できるように、単純に作成しないのはなぜですか?私はそれが常にあなたのデザインとベースクラスがどのように適合するかによって決まると思います。派生クラスメーカーは必要なロジックの実装に集中すべきだと私は主張します。他のすべてはすでに処理されています。また、カプセル化の問題もあります。

C ++の観点から見ると、クラスから呼び出すことができなくても、プライベート仮想メソッドをオーバーライドすることは完全に正当です。これは、上記の設計をサポートします。


3

私はそれらを使用して、派生クラスが基本クラスの「空白を埋める」ことを可能にし、そのような穴をエンドユーザーに公開しません。たとえば、共通のベースから派生する非常にステートフルなオブジェクトがあり、これは状態マシン全体の2/3しか実装できません(派生クラスは、テンプレート引数に応じて残りの1/3を提供し、ベースはその他の理由)。

多くのパブリックAPIを正しく機能させるために、共通の基本クラスが必要です(可変テンプレートを使用しています)が、そのオブジェクトを公開することはできません。さらに悪いことに、クレーターを純粋な仮想関数の形で「プライベート」以外のどこかにステートマシンに残すと、その子クラスの1つから派生した賢いユーザーまたは無知なユーザーが、ユーザーが決して触れてはならないメソッドをオーバーライドできるようになります。そこで、ステートマシンの「頭脳」をPRIVATE仮想関数に入れました。次に、基本クラスの直接の子が非仮想オーバーライドの空白を埋め、ユーザーはステートマシンの混乱を心配することなく、結果のオブジェクトを安全に使用したり、独自の派生クラスを作成したりできます。

パブリック仮想メソッドを持ってはいけないという議論については、私はBSと言います。ユーザーは、パブリック仮想と同じくらい簡単にプライベート仮想を不適切にオーバーライドできます。結局、新しいクラスを定義しています。公衆が特定のAPIを変更してはならない場合は、公にアクセス可能なオブジェクトで仮想AT ALLにしないでください。

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