1

(ここでダイヤモンドと仮想の継承に関する質問を検索して読みましたが、答えを見つけることができませんでした。私の考えでは、この状況は少し異常であり、私の要件が多少ずれているという考えを喜んで受け入れるつもりです。オン一方、これは「いい」方法で実行できるはずだと思います。)

状況と要件:

私は、自分では制御できず、変更できない C++ クラス ライブラリを持っています。クラスを定義しWindowます。クラスには、派生クラスが使用することを意図した、他の方法ではアクセスできないWindow保護されたメンバー (たとえば: ) があります。何百もの(まあ、非常に多数の...)メソッドを定義しています。handleWindow

に機能を追加してWindow、派生クラス (私が書いた; と言うLogWindow) が自動的に持つようにしたいと考えています。このような機能の例として、ウィンドウを相互にスナップする機能があります。これを実装するには、Windowの保護されたhandleメンバーにアクセスする必要があります。

私の実際の目的にはこれで十分であり、解決策はSnappableWindow簡単です。WindowWindowLogWindowSnappableWindow

しかし、私が本当に欲しいのは、よりきれいな私見ですが、次のとおりです。

  1. この「Snappable」機能をスタンドアロンのコードとして持つ機能。他のWindow派生クラスに「プラグイン」するかどうかを選択できます。
  2. この概念を他の機能にも拡張する機能 (たとえば、ウィンドウを最小化する機能)。したがってWindow、「スナップ可能」機能の有無にかかわらず、「最小化可能」機能の有無にかかわらず、派生クラスを持つことができます。
  3. 「SnappableWindow」と「MinimizableWindow」の実装は、どちらもWindowhandle保護されたメンバーにアクセスする必要があります。
  4. 「スナップ可能」と「最小化可能」を実際のクラス宣言の一部にして、実際のクラス ( LogWindow) が「ウィンドウ」、「スナップ可能ウィンドウ」、「最小化可能ウィンドウ」になるようにしたいと考えています。

...そして今、質問に:

SnappableWindowandMinimizableWindowを宣言することでこれを行う方法を理解しますから派生するのではなく、コンストラクターでWindowa を取得し、と の任意の組み合わせから派生します。handleLogWindowWindowSnappableWindowMinimizableWindow

編集:の init()を呼び出した後、LogWindow のコンストラクターの途中handleで初期化されます。(前に述べたように、 のコンストラクターの途中ではありません)。WindowWindowWindow

ただしhandleは のコンストラクタの途中でしか初期化されないため (のLogWindowを呼び出した後)、のコンストラクタ初期化リストの一部としておよびに渡すことはできません。むしろ、両方で何らかのメソッドを明示的に呼び出して、 . そして、これは、私の派生クラスのそれぞれにあります。( 、、など)Windowinit()SnappableWindowMinimizableWindowLogWindowinit()handleWindowLogWindowSearchWindowPreferencesWindow

次のようなことができる方法を探しています。

クラス LogWindow : public Window、public SnappableWindow、public MinimizableWindow

... 内に他のものを実装する必要はありませんLogWindow。仮想継承をいじりましたが、解決策が思いつきません。

4

3 に答える 3

1

仮想継承はこれを処理できる必要があります。

class SnappableWindow: virtual public Window

class MinimizableWindow: virtual public Window

class LogWindow: virtual public Window, public SnappableWindow, public MinimizableWindow

三角形はひし形の特殊なケースであることに注意してください。

Window
|  \  \---------------\
|   \                  \
|    SnappableWindow    MinimizableWindow
|   /                  /
|  /    /-------------/
LogWindow

編集:ここに完全な例があります:

#include <iostream>
int get_handle() { static int handle = 0; return ++handle; }
struct Window {
    int handle;
    Window() : handle(get_handle()) { }
};
struct SnappableWindow: virtual public Window {
    SnappableWindow() { std::cout << "Snap! " << handle << std::endl; }
};
struct MinimizableWindow: virtual public Window {
    MinimizableWindow() { std::cout << "Mm! " << handle << std::endl; }
};
struct LogWindow: virtual public Window, public SnappableWindow, public MinimizableWindow {
    LogWindow() { std::cout << "Log! " << handle << std::endl; }
};
int main() {
    LogWindow lw;
    std::cout << "lw: " << lw.handle << std::endl;
}

出力:

Snap! 1
Mm! 1
Log! 1
lw: 1
于 2012-07-13T22:13:37.013 に答える
1

そのために特性を使用できますが、残念ながら、保護されたメンバーにアクセスできません。保護されたメンバーを公開する中間クラスを作成すると、それを行うことができます。それが理にかなっていることを確認してください:

struct Window {
protected:
    int handle;
};
struct BaseWindow : public Window {
    int get_handle() { return handle; }
};
template <class TWindow>
struct Snappable {
    Snappable() { std::cout << "Snappable " << self()->get_handle() << std::endl; }
private:
    TWindow *const self() {
        return static_cast<TWindow*>(this);
    }
};
template <class TWindow>
struct Minimizable {
    Minimizable() { std::cout << "Minimizable " << self()->get_handle() << std::endl; }
private:
    TWindow *const self() {
        return static_cast<TWindow*>(this);
    }
};
struct LogWindow: public BaseWindow, public Snappable<LogWindow>, public Minimizable<LogWindow> {
};

特性を使用する興味深いアーキテクチャ スタイルについては、こちらを参照してください。

于 2012-07-14T01:33:59.900 に答える
0

これは実際には少し紛らわしいです...ハンドルがコンストラクターの一部として初期化されている場合、コンストラクターが初期化子リストで完了したWindow後に使用可能になります。Window

class Window {
protected:
   int handle;
   Window() { handle = 5; }
};
struct Snappable {
   int hdl;
   Snappable( int handle ) : handle(handle) {}
};
struct MyWindow : Window, Snappable {       // Order matters here!
   MyWindow() : Window(), Snappable( handle ) {}
};
int main() {
   MyWindow w;
   std::cout << w.hdl << std::endl;         // 5
}

基本コンストラクターの実行順序は、初期化子リストのシーケンスではなく、クラス定義の宣言の順序であることに注意することが重要です。

そうは言っても、これが良いデザインかどうかは別の問題です。

于 2012-07-13T21:50:34.207 に答える