最近、クラスが特定の基本クラスから継承する(クライアントコードに対して)事実を隠蔽するために、保護された継承を使用するコードについて議論しましたが、実装でこの事実を悪用しています。
次のコードはこれを示しています。 GCCとclang ++の最新バージョンでコンパイルします(C++ 11機能を使用します):
#include <vector>
#include <iostream>
class IObserver
{
public:
virtual void update() = 0;
};
class Model
{
std::vector<IObserver*> m_observers;
int m_number = 0;
public:
void addObserver(IObserver& observer) {
m_observers.Push_back(&observer);
}
void setNumber(int value) {
m_number = value;
notifyObservers();
}
int number() const {
return m_number;
}
protected:
void notifyObservers() {
for (auto pObserver : m_observers)
pObserver->update();
}
};
// We want to hide the fact class 'View' has 'IObserver' interface.
class View : protected IObserver
{
Model* m_pModel;
public:
View(Model& model) : m_pModel(&model) {
model.addObserver(*this); // Exploit the fact we are an 'IObserver'.
}
protected:
void update() override {
std::cout << m_pModel->number() << std::endl;
}
};
int main(int argc, char *argv[])
{
Model model;
View view(model);
//view.update(); // ERROR: 'update' is a protected member of 'View'.
model.setNumber(1);
model.setNumber(2);
}
'View'クラスは 'IObserver'インターフェイスを継承しますが、 'protected'修飾子を使用します。したがって、それに応じて、そのインターフェイスから継承されたパブリックメソッド「update」は保護されます。コンストラクターでは、クラス「View」がパラメーターとして渡された「Model」インスタンスにオブザーバーとして自分自身を追加します。実行可能ファイルを実行すると、「1」と「2」が2行に分かれて出力されるため、コードは期待どおりに実行されます。
現在、このソリューションは私たちのチームで激しく議論されましたが、最終的には、共通の合意では答えられないいくつかの質問がありました。
これら3つの質問についてのあなたの意見をたくさん感謝します!!!事前に感謝します:-)
コンパイルされているので、C++は合法(つまり有効)であると判断できます。あなたはGCCとclangでそれを使用しました、そして私はコンパイラ市場の大部分をカバーするようにMSVC++でまったく同じことをしました。
これが、protected/private(ところで、私はprivateを使用します)修飾子が導入された理由です。それはあなたのクラスが内部で使用している他のクラスに必要なコールバックインターフェースを実装することを可能にします、しかしa)他の世界はあなたがそれらの他のクラスを使用していることを知る必要はなく、そしてb)他の世界はすべきではありませんこれらの関数は非表示にして内部でのみ使用することを目的としているため、これらの関数を呼び出すこともできます。
C++からC#に移行したとき、実際にはプライベートインターフェイスの継承ができないことに不思議に思いました。何かが実装の詳細であり、一般に公開/使用されることを意図していない場合は、非表示にしておく必要があります。あなたが説明した方法は、それを行うための優れた簡単な方法だと思います。
PImplを使用すると、オーバーヘッドが増えるだけです。オブジェクトを割り当てると(明らかに動的割り当てを想定しています)、ヒープに2倍の回数が行き着きます。また、ViewとViewImplがメモリ内の別のページにあり、キャッシュミスが増えるため、コードの局所性も削減します。また、すべてのパブリックインターフェイス呼び出しをViewからViewImplに転送しているため、作成する必要のあるボイラープレートコードの量が増えました。
これはすべて私の意見ですが、結局のところ、プライベートインターフェイスの継承を持たない理由は考えられません。私はこのようなコードを6年以上作成してきましたが、そのために読みやすさや保守性に問題はありませんでした。