58

オーバーライドされたC++仮想関数のアクセス許可を基本クラスとは異なるものにする理由はありますか?そうすることで何か危険はありますか?

例えば:

class base {
    public:
        virtual int foo(double) = 0;
}

class child : public base {
    private:
        virtual int foo(double);
}

C ++のよくある質問には、それは悪い考えだと書かれていますが、その理由は書かれていません。

私はいくつかのコードでこのイディオムを見てきましたが、プライベートメンバー関数をオーバーライドすることは不可能であるという仮定に基づいて、作成者がクラスを最終的にしようとしていたと思います。ただし、この記事では、プライベート関数をオーバーライドする例を示します。もちろん、C++に関するよくある質問の別の部分ではそうしないことを推奨しています。

私の具体的な質問:

  1. 派生クラスと基本クラスで仮想メソッドに異なる権限を使用することに技術的な問題はありますか?

  2. そうする正当な理由はありますか?

4

7 に答える 7

42

子がいる場合はfooを呼び出せないという驚くべき結果が得られますが、それをベースにキャストしてからfooを呼び出すことはできます。

child *c = new child();
c->foo; // compile error (can't access private member)
static_cast<base *>(c)->foo(); // this is fine, but still calls the implementation in child

基本クラスのインスタンスとして扱っている場合を除いて、関数を公開したくない例を考え出すことができるかもしれません。しかし、その状況が発生したという事実は、おそらくリファクタリングが必要な、ラインのどこかに悪いOOデザインがあることを示唆しています。

于 2009-01-27T18:27:21.647 に答える
34

問題は、基本クラスのメソッドがそのインターフェイスを宣言する方法であることです。本質的には、「これらは、このクラスのオブジェクトに対して実行できることです」と言っています。

Derived クラスで、Base がパブリック プライベートとして宣言したものを作成すると、何かが奪われます。ここで、派生オブジェクトが「ベース オブジェクト」であるにもかかわらず、派生クラス オブジェクトに対しては実行できない、ベース クラス オブジェクトに対して実行できるはずの何かが、リスコフの置換原理を破っています。

これにより、プログラムに「技術的な」問題が発生しますか? そうでないかもしれない。しかし、それはおそらく、クラスのオブジェクトが、ユーザーが期待するように動作しないことを意味します。

これが必要な状況に陥った場合 (別の回答で参照されている非推奨のメソッドの場合を除いて)、継承が実際には「is-a」をモデル化していない継承モデルがある可能性があります (たとえば、Scott Myers の例の Square は Rectangle を継承していますが、Square の幅を高さとは無関係に変更することはできません (長方形の場合のように変更することはできません)。また、クラスの関係を再検討する必要がある場合があります。

于 2009-01-27T19:00:40.363 に答える
6

技術的な問題はありませんが、公開されている関数は、ベースポインタと派生ポインタのどちらを使用しているかによって異なる状況になります。

私の意見では、これは奇妙で紛らわしいでしょう。

于 2009-01-27T18:26:46.017 に答える
4

これは、プライベート継承を使用している場合に非常に便利です。つまり、基本クラスの (カスタマイズされた) 機能を再利用したいが、インターフェイスは再利用したくない場合です。

于 2009-01-27T19:17:44.090 に答える
3

それは可能であり、ごくまれに利益につながるでしょう。たとえば、コードベースでは、以前使用していたパブリック関数を持つクラスを含むライブラリを使用していますが、他の潜在的な問題のために使用を推奨していません(より安全なメソッドを呼び出すことができます)。また、そのクラスから派生したクラスがあり、多くのコードが直接使用しています。そこで、派生クラスで指定された関数をプライベートにして、誰もがそれを助けることができる場合はそれを使用しないことを忘れないようにしました。それはそれを使用する能力を排除するものではありませんが、コードレビューの後半ではなく、コードがコンパイルしようとするときにいくつかの使用法をキャッチします。

于 2009-01-27T18:41:57.797 に答える
1
  1. 隠れたランタイムコストがあるため、技術的な意味であれば、技術的な問題はありません。
  2. ベースを公に継承する場合は、これを行うべきではありません。保護またはプライベートを介して継承する場合、これは、ベースポインタがない限り意味のないメソッドの使用を防ぐのに役立ちます。
于 2009-01-27T18:42:02.150 に答える
1

プライベート継承の適切な使用例は、Listener/Observer イベント インターフェイスです。

プライベート オブジェクトのコード例:

class AnimatableListener {
  public:
    virtual void Animate(float delta_time);
};

class BouncyBall : public AnimatableListener {
  public:
    void TossUp() {}
  private:
    void Animate(float delta_time) override { }
};

オブジェクトの一部のユーザーは親の機能を必要とし、一部のユーザーは子の機能を必要とします:

class AnimationList {
   public:
     void AnimateAll() {
       for (auto & animatable : animatables) {
         // Uses the parent functionality.
         animatable->Animate();
       }
     }
   private:
     vector<AnimatableListener*> animatables;
};

class Seal {
  public:
    void Dance() {
      // Uses unique functionality.
      ball->TossUp();
    }
  private:
    BouncyBall* ball;
};

このようにAnimationListして、 は親への参照を保持し、親機能を使用できます。はSeal子への参照を保持し、一意の子機能を使用し、親の機能を無視します。この例では、Sealは を呼び出すべきではありませんAnimate。前述のようにAnimate、ベース オブジェクトにキャストすることで呼び出すことができますが、これはより困難であり、通常は行うべきではありません。

于 2017-01-05T19:26:40.993 に答える