オーバーライドされたC++仮想関数のアクセス許可を基本クラスとは異なるものにする理由はありますか?そうすることで何か危険はありますか?
例えば:
class base {
public:
virtual int foo(double) = 0;
}
class child : public base {
private:
virtual int foo(double);
}
C++ faq は悪い考えだと言っていますが、理由は述べていません。
私はいくつかのコードでこのイディオムを見てきましたが、プライベートメンバー関数をオーバーライドすることは不可能であるという仮定に基づいて、作成者がクラスを最終的にしようとしていたと思います。ただし、 この記事 は、プライベート関数をオーバーライドする例を示しています。もちろん C++のよくある質問の別の部分 そうしないことをお勧めします。
私の具体的な質問:
派生クラスと基本クラスで仮想メソッドに異なる権限を使用することに技術的な問題はありますか?
そうする正当な理由はありますか?
問題は、Baseクラスのメソッドがそのインターフェースを宣言する方法であるということです。本質的には、「これらは、このクラスのオブジェクトに対して実行できることです」と言っています。
Derivedクラスで、Baseがパブリックプライベートとして宣言したものを作成すると、何かを奪うことになります。さて、派生オブジェクトは「is-a」ベースオブジェクトですが、ベースクラスオブジェクトに対して実行できるはずのことは、派生クラスオブジェクトに対しては実行できず、 Liskov Substitution Prinicple を破ります。
これにより、プログラムに「技術的な」問題が発生しますか?そうでないかもしれない。しかし、それはおそらく、クラスのオブジェクトが、ユーザーが期待するように動作しないことを意味します。
これが必要な状況にある場合(別の回答で参照されている非推奨のメソッドの場合を除く)、継承が実際には「is-a」をモデル化していない継承モデルがある可能性があります(たとえば、Scott Myersの例のSquareはRectangleを継承していますが、Squareの幅を長方形の場合のように高さに依存せずに変更することはできず、クラスの関係を再検討する必要がある場合があります。
子がいる場合、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設計があることを示唆しています。
技術的な問題はありませんが、公開されている関数は、ベースポインタと派生ポインタのどちらを使用しているかによって異なる状況になります。
私の意見では、これは奇妙で紛らわしいでしょう。
プライベート継承を使用している場合、つまり、インターフェイスではなく基本クラスの(カスタマイズされた)機能を再利用したい場合に非常に便利です。
それは可能であり、ごくまれに利益につながるでしょう。たとえば、コードベースでは、以前使用していたパブリック関数を持つクラスを含むライブラリを使用していますが、他の潜在的な問題のために使用を推奨していません(より安全なメソッドを呼び出すことができます)。また、コードの多くが直接使用する、そのクラスから派生したクラスもあります。そのため、派生クラスで指定された関数をプライベートにして、誰もがそれを助けることができる場合はそれを使用しないことを忘れないようにしました。それはそれを使用する能力を排除するものではありませんが、コードレビューの後半ではなく、コードがコンパイルしようとするときにいくつかの使用法をキャッチします。
プライベート継承の適切なユースケースは、リスナー/オブザーバーイベントインターフェイスです。
プライベートオブジェクトのサンプルコード:
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
はベースオブジェクトにキャストすることで呼び出すことができますが、それはより困難であり、通常は実行すべきではありません。