vendredi 24 novembre 2017

Accessing outer object from the nested one

Question about C++ standard and appropriate design pattern

I have an object which has several non-POD objects as members, and from within those objects I need to access the outer one.

For example it can be an object which "listens" to several different events, whereas listening means implementing some interface. You can't just inherit this interface directly by your main class, because you can't inherit the same base class multiple times. But even if you could - you may want to avoid this, since you don't want to expose this interface, it's just implementation details of your object.

I mean something like this:

class MyClass
{
    // ...
    struct Listener1 :public IEventListener {
        virtual void OnEvent() { /* need to access MyClass */ }
    } m_Evt1;

    struct Listener2 :public IEventListener {
        virtual void OnEvent() { /* need to access MyClass */ }
    } m_Evt2;
};

One obvious way to give access to the outer class is to have a member of pointer/reference to it in every inner object. But this requires writing some code (initialization of the pointer/reference), and compilers don't like when you use this in base member initialization, plus there's an actual code generated for this and object layout is inflated in memory.

There is also another possibility: using offsetof-like trick. The memory offset of the inner object wrt outer class is known at compile-time. Hence vice-versa also holds, i.e. we can subtract that offset from this of the inner object to get the pointer to the outer class. Wrap it with nice C++ accessor method and - voila.

#define IMPLEMENT_GET_PARENT_OBJ(parent_class, this_var) \
parent_class& get_ParentObj() { return * (parent_class*) (((PBYTE) this) + 1 - (PBYTE) (&((parent_class*) 1)->this_var)); }

class MyClass
{
    // ...
    struct Listener1 :public IEventListener {
        IMPLEMENT_GET_PARENT_OBJ(MyClass, m_Evt1)
        virtual void OnEvent() { get_ParentObj().OnEvent1(); }
    } m_Evt1;

    struct Listener2 :public IEventListener {
        IMPLEMENT_GET_PARENT_OBJ(MyClass, m_Evt2)
        virtual void OnEvent() { get_ParentObj().OnEvent2(); }
    } m_Evt2;

    void OnEvent1();
    void OnEvent2();
};

One can argue that if you declare such objects of inner type outside of the MyClass - then obviously the get_ParentObj() is broken, and you get the undefined behavior. Also you may technically get the reference to the outer class from the c'tor of the inner one, and invoke its methods even before it's constructed.

I know there are zillions of ways to solve this and make it fool-proof, such as creating inner objects dynamically and giving them a weak reference to the outer class, and etc. But this is an overkill in my scenario. Assuming you don't shoot yourself in the foot on purpose there seems to be no problem. And speaking practically it just works, without any memory waste or unneeded extra code.

Is it considered a good practice? And if not - why?

Aucun commentaire:

Enregistrer un commentaire