mercredi 22 novembre 2017

Questions regarding the C++ standard complience

I'm C/C++ programmer for a long time, my exerience mostly relates to low-level programming, optimizations, debugging and etc, and I know how things work practicaly "behind the scenes". My weak point, however, is the C/C++ standard. I'm using some useful "tricks", and they are very convenient. I'd like someone to have a "fresh look" on them to spot potential problems and/or standard violations.

1. Type casting. Sometimes you want to upcast a pointer/reference, sometimes you need to downcast it explicitly (for instance, to pass to specific overloaded function), and sometimes you need to remove the const specifier. One may argue that those are workarounds and signs of inferior design, but in real-worls big projects this is virtually unavoidable. Of course you may directly use C/C++ cast syntax, or static_cast, but those constructs don't guarantee at compile-time that the target type is indeed in child-parent relation with the source one.

So, I use the following templates:

template <typename TT, typename T> TT& UpCast(T& x)
{
    TT& ret = (TT&) x;
    T& unused = ret; // won't compile unless T is a base of TT
    return ret;
}
template <typename TT, typename T> TT* UpCast(T* p)
{
    TT* ret = (TT*) p;
    T* unused = ret; // won't compile unless T is a base of TT
    return ret;
}
template <typename TT, typename T> TT& DownCast(T& x)
{
    return x; // won't compile unless TT is a base of T
}
template <typename T> T& NotConst(const T& x) { return (T&) x; }
template <typename T> T* NotConst(const T* p) { return (T*) p; }

Then, the code looks like this:

Derived& derived = UpCast<Derived>(base);
Base& base = DownCast<Base>(derive);

const MyClass& cx;
MyClass& x = NotConst(cx);

This way IMHO intentions are more clear, there's a compile-time guarantee of type compatibility, and type casts don't hurt the eyes. I'm aware that there are complex cases with multiple inheritance where casting is ambiguous, but apart of this there seems to be no problem. Is it?

2. Accessing outer object from the inner one. Sometimes you have an inner non-POD object as a member in an outer class. This is a common practice actually, for example you may have 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();
    } m_Evt1;

    struct Listener2 :public IEventListener {
        virtual void OnEvent();
    } m_Evt2;
};

Then, within OnEvent of both inner classes you want to access the outer MyClass. One obvious way to achieve this 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();
};

This looks nice, works nice, and no unneeded code and memory waste. 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. But assuming you don't shoot youself in the foot there seems to be no problem. Is it?

Aucun commentaire:

Enregistrer un commentaire