Pergunta

(I searched and read thru the Diamond- and virtual-inheritance questions here, but could not find an answer. My thinking is that this situation is a little unusual, and I am willing to entertain the ideas that my requirements are somewhat off. On the other hand, I think this should be doable in a "nice" way.)

The situation and the requirements:

I have a C++ class library over which I have no control, and which I cannot change. It defines a Window class. The Window class has a protected member (say: handle), not otherwise accessible, that derived classes are meant to use. Window defines hundreds (well, a very large number...) of methods which I don't care to re-implement by delegating (in a decorator, say).

I want to add functionality to Window, so that derived classes (which I write; say LogWindow) automatically have. An example of such functionality is the ability to snap windows to each other. In order to implement this I need access to Window's protected handle member.

For my real-life purpose this is enough, and the solution is simple: derive SnappableWindow from Window, and derive all my Window-derived classes (LogWindow in this example) from SnappableWindow.

However, what I would really want, and is prettier IMHO, is:

  1. The ability to have this "Snappable" functionality as a standalone piece of code, which I can choose to "plug into" any other Window-derived class, or not.
  2. The ability to extend this notion to other functionalities as well, for example the ability to minimize windows. So I could have a Window-derived class, with or without the "Snappable" ability, and with or without the "Minimizable" ability.
  3. The implementations of "SnappableWindow" and "MinimizableWindow" both need access to Window's handle protected member.
  4. I would like "Snappable" and "Minimizable" to be part of the actual class declaration, so that my actual class (LogWindow) "is a" Window, "is a" SnappableWindow, and "is a" MinimizableWindow.

... and now to the question:

I get how I can do this with declaring SnappableWindow and MinimizableWindow as not deriving from Window but rather getting a handle in their constructor, and then deriving LogWindow from Window and from any combination of SnappableWindow and MinimizableWindow.

EDIT: handle is initialized, in Window, half-way thru LogWindow's constructor, after it has called Window's init(). (and not half way thru Window's constructor, as I've said before).

However, since handle is only initialized half way thru LogWindow's constructor (after it has called Window's init()) , I can't pass is to SnappableWindow and MinimizableWindow as part of LogWindow's constructor initialization list. Rather, I would have to explicitly call some init() method on both, passing it the handle. And this, in each of my Window-derived classes. (LogWindow, SearchWindow, PreferencesWindow, etc.)

I am looking for a way to be able to do something like:

class LogWindow : public Window, public SnappableWindow, public MinimizableWindow

... and not have to implement anything else inside LogWindow. I've fiddled with virtual inheritance, but can't quite come up with the solution.

Foi útil?

Solução

Virtual inheritance should be able to handle this:

class SnappableWindow: virtual public Window

class MinimizableWindow: virtual public Window

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

Note that a triangle is just a special case of a diamond!

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

Edit: here's a full example:

#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;
}

Output:

Snap! 1
Mm! 1
Log! 1
lw: 1

Outras dicas

You could use traits for that, but unfortunately they can't access protected members. You can do it if you create an intermediate class that exposes the protected member. See if it makes sense:

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> {
};

Look here for an interesting architectural style that uses traits.

This is actually a bit confusing... If the handle is initialized as part of the Window constructor, then it will be available after Window constructor completes in the initializer list:

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
}

It is important to note that the order of execution of the base constructors is that of the declaration in the class definition, not the sequence in the initializer list.

That being said, whether this is a good design or not is a different question.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top