2

I would like to implement 'GetParent()' function in here-

class ChildClass;

class ParentClass
{
public:
    ....
    ChildClass childObj;
    ....
};

class ChildClass
{
    friend class ParentClass;
private:
    ChildClass();

public:
    ParentClass* GetParent();
};

I've tried to create a private member variable which stores pointer to parent object. However this method requires additional memory.

class ChildClass
{
    friend class ParentClass;

private:
    ChildClass();

    ParentClass* m_parent;

public:
    ParentClass* GetParent()
    {
        return m_parent;
    }
};

So I used offsetof() macro (performance costs of calling offsetof() can be ignored), but I'm not sure this approach is safe. Would it be work on every situation? Is there any better Idea?

class ChildClass
{
public:
    ParentClass* GetParent()
    {
        return reinterpret_cast<ParentClass*>(
            reinterpret_cast<int8_t*>(this) - offsetof(ParentClass, childObj)
            );
    }
};
  • 6
    What do you _really_ want to achieve ? – Richard Dally Dec 29 '15 at 13:14
  • 2
    You can't have a member variable of incomplete type `ChildClass`. In other words, you have a circular dependency. – eerorika Dec 29 '15 at 13:15
  • Also, why are you worrying about a pointer wasting memory? How many instances of `ChildClass` do you intend to have? Why is your constructor `private`? Please be more specific or we can't help you. Your question is somewhat unclear, that's why the downvote (wasn't me though, I think this question _may_ have some potential) – LHLaurini Dec 29 '15 at 13:20
  • Read the restrictions on the types you can use with `offsetof`. You can't use it here. – Pete Becker Dec 29 '15 at 13:27
  • RAM is cheap. RAM prices are at their historic lows. – Sam Varshavchik Dec 29 '15 at 13:29
  • What is `e[index]`? The second parameter of `offsetof` must be the name of the member. – eerorika Dec 29 '15 at 13:38
  • 1
    @SamVarshavchik: Yeah but not everybody is programming for a modern desktop PC or the latest smartphone. Some of us are making the technology that powers the world, which often has serious constraints. We want to make the most of the memory we have. – Lightness Races in Orbit Dec 29 '15 at 13:44
  • `offsetof` can only be used on standard layout classes. this will only work if you keep your parent and child class simple, and you make sure never to create any child class that is not part of a parent class – M.M Dec 31 '15 at 03:44

2 Answers2

3

Calculating the address of the container object using offsetof is safe in the sense that it can work. offsetof is commonly used in C for this purpose. See for example the container_of macro in Linux kernel.

It can be unsafe in the sense that if there is a ChildClass instance that is not that particular member variable, then you have undefined behaviour on your hands. Of course, since the constructor is private, you should be able to prevent that.

Another reason why it's not safe is that it has undefined behaviour if the container type is not a standard layout type.

So, it can work as long as you take the caveats into account. Your implementation however, is broken. The second parameter of the offsetof macro must be the name of the member. In this case, it must be childObj and not e[index] which is not the name of the member.

Also (maybe someone will correct me if I'm wrong, but I think) casting to an unrelated type uint8_t* before doing the pointer arithmetic and then cast to yet another unrelated type seems a bit dangerous. I recommend using char* as the intermediate type. It is guaranteed that sizeof(char) == 1 and it has special exceptions about aliasing and not having trap representations.

It might be worth mentioning that this use - or any use other than use with arrays - of pointer arithmetic is not defined by the standard. Which strictly speaking makes offsetof useless. Still, pointers are widely used beyond arrays, so the lack of standard backing can in this case be ignored.

Community
  • 1
  • 1
eerorika
  • 232,697
  • 12
  • 197
  • 326
3

Here a more general solution for future visitors:

#include <cstddef>
#include <type_traits>

template <class Struct, std::size_t offset, class Member>
Struct &get_parent_struct_tmpl(Member &m){
    static_assert(std::is_standard_layout<Struct>::value,
                  "Given struct must have a standard layout type");
    return *reinterpret_cast<Struct *>(reinterpret_cast<char *>(&m) - offset);
}
#define get_parent_struct(STRUCTNAME, MEMBERNAME, MEMBERREF)\
    get_parent_struct_tmpl<STRUCTNAME, offsetof(STRUCTNAME, MEMBERNAME)>(MEMBERREF)

Test case:

#include <cassert>

struct Foo{
    double d;
    int i;
    bool b;
    char c;
    bool b2;
};

int main(){    
    Foo f;
    bool &br = f.b;

    Foo &fr = get_parent_struct(Foo, b, br);

    assert(&fr == &f);
}

There is a static_assert that defends against UB caused by the given struct not having standard layout as mentioned by user2079303.

The code as shown requires C++11, however, you can delete #include <type_traits> and the static_assert to make it compile in C++03, however, you will have to manually make sure you have a standard layout type.

Community
  • 1
  • 1
nwp
  • 9,623
  • 5
  • 38
  • 68