0

What I am needing can be done by storing this pointer of enclosing class into nested class for example this way:

class CEnclosing {
public:
    class CNested : public CSomeGeneric {
    public: 
        CNested(CEnclosing* e) : m_e(e) {}
        virtual void operator=(int i) { m_e->SomeMethod(i); }
        CEnclosing* m_e;
    };

    CNested nested;

    CEnclosing() : nested(this) {}

    virtual void SomeMethod(int i);
};


int main() 
{
    CEnclosing e;
    e.nested = 123;
    return 0;
}

This works well, but requires sizeof(void*) bytes of memory more for each nested member class. Exist effective and portable way to do this without need to store pointer to instance of CEnclosing in m_e?

Jarod42
  • 203,559
  • 14
  • 181
  • 302
user3123061
  • 757
  • 5
  • 14
  • 1
    I think storing a pointer is a popular approach. The memory doesn't seem to be that much of an overhead. You could also send the pointer as an argument. – keyser Feb 01 '14 at 13:48
  • 2
    If `CEnclosing` is a standard-layout class (=> no virtual functions), you can use `offsetof` to get the offset of the member `nested` inside an object of type `CEnclosing`. I'd recomment making `CNested` a private nested class, or somehow forbid creating instanced *not* inside a `CEnclosing` object if you want to use `offsetof` (also `static_assert` for `std::is_standard_layout`). – dyp Feb 01 '14 at 14:09
  • Possible duplicate of [get-address-of-a-non-pod-object-from-within-a-data-member-which-is-a-single-use](http://stackoverflow.com/questions/11856480/get-address-of-a-non-pod-object-from-within-a-data-member-which-is-a-single-use) – Jarod42 Feb 01 '14 at 14:16
  • A virtual function in a class of which you only have a data member (not a pointer/reference) looks weird. – dyp Feb 01 '14 at 14:22
  • One way to do composition in C++ is "private inheritance", which in your case also provides "empty base class optimization". You can also use CRTP for getting rid of `this` pointer – zahir Feb 01 '14 at 15:48

4 Answers4

2

As stated previously, C++ does not provide any way to do this. A nested class has no special way to find its enclosing class. The solution you already have is the recommended way.

If you have an advanced scenario, and if you are prepared to maintain non-portable code, and if the cost of storing an additional pointer is important enough to use a risky solution, then there is a way based on the C++ object model. With a number of provisos I won't go into, you can rely on the enclosing and nested classes being laid out in memory in a predictable order, and there being a fixed offset between the start of the enclosing and nested classes.

The code is something like:

   CEnclosing e;
   int offset = (char*)&e.nested - (char*)&e;
   //... inside nested class
   CEnclosing* pencl = (CEnclosing*)((char*)this - offset);

OTOH it's equally possible that the offsetof macro may just do it for you, but I haven't tried it.

If you really want to do this, read about trivially copyable and standard layout in the standard.

david.pfx
  • 10,520
  • 3
  • 30
  • 63
  • 1
    Better to use `offsetof`. – Jarod42 Feb 01 '14 at 14:30
  • 1
    I agree, but the standard says it's undefined unless it's a standard-layout class, so you run a real risk of a compiler diagnostic. – david.pfx Feb 01 '14 at 14:41
  • 1
    @david.pfx IIRC, a `reinterpret_cast` to `char*` is also implementation-defined. Better be clear about what you're doing, and maybe add some `static_assert`s if possible. – dyp Feb 01 '14 at 15:40
  • I agree about the asserts, and I agree I should be more careful about nominating the kind of cast. However, the standard requires pointers to 'round trip' and requires char to be the smallest integral type, so I don't think there is much room for an implementation to mess with this one. I could be wrong. :) – david.pfx Feb 02 '14 at 00:32
2

I believe the following could be portable; though it is not fool-proof. Specifically, it will not work across virtual inheritance.

Also, I would like to point that it is not safe, in that it will happily compile even if the member you pass does not correspond to the one you compute the offset with:

#include <iostream>

template <typename C, typename T>
std::ptrdiff_t offsetof_impl(T C::* ptr) {
    C c; // only works for default constructible classes
    T* t = &(c.*ptr);
    return reinterpret_cast<char*>(&c) - reinterpret_cast<char*>(t);
}

template <typename C, typename T, T C::* Ptr>
std::ptrdiff_t offsetof() {
    static std::ptrdiff_t const Offset = offsetof_impl(Ptr);
    return Offset;
}

template <typename C, typename T, T C::* Ptr>
C& get_enclosing(T& t) {
    return *reinterpret_cast<C*>(reinterpret_cast<char*>(&t)
         + offsetof<C, T, Ptr>());
}

// Demo
struct E { int i; int j; };

int main() {
    E e = { 3, 4 };

    //
    // BEWARE: get_enclosing<E, int, &E::j>(e.i); compiles ERRONEOUSLY too.
    //                                   ^ != ^
    //
    E& ref = get_enclosing<E, int, &E::j>(e.j);

    std::cout << (void const*)&e << " " << (void const*)&ref << "\n";
    return 0;
}

Still, it does run on this simplistic example, which allowed me to find 2 bugs in my initial implementation (already). Handle with caution.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
1

The clear and simple answer to your question is no, C++11 doesn't have any special feature to handle your scenario. But there is a trick in C++ to allow you to do this:

If CEnclosing didn't have a virtual function, a pointer to nested would have the same value as a pointer to the containing instance. That is:

(void*)&e == (void*)&e.nested

This is because the variable nested is the first in the class CEnclosing.

However, since you have a virtual function in CEnclosing class, then all you need to do is subtract the vtable size from &e.nested and you should have a pointer to e. Don't forget to cast correctly, though!


EDIT: As Stephane Rolland said, this is a dangerous solution and, honestly, I wouldn't use it, but this is the only way (or trick) I could think of to access the enclosing class from a nested class. Personally, I would probably try to redesign the relation between these two classes if I really want to optimise memory usage up to the level you mentioned.

Rafid
  • 18,991
  • 23
  • 72
  • 108
  • 3
    If there is inheritance, (CEnclosing is a subclass), there are also chances there is an offset because of the base class object in memory. Your solution is **highly dangerous**. I wouldn't recommend that. – Stephane Rolland Feb 01 '14 at 13:53
  • I agree with you, it is a 'trick' as I said in my answer, and I don't think it is guaranteed to work on all compilers (though I tried similar solutions in VC++ and they do work), but that is the only trick I could think of to answer the SO question. Let me clarify this in my answer. – Rafid Feb 01 '14 at 13:56
  • 1
    Your answer should also reflect to the OP that he is worrying for the size of pointer in memory, which is really small, and using your trick is really dangerous compared to what he is worrying about. – Stephane Rolland Feb 01 '14 at 13:58
  • 1
    It's not actually 'dangerous'. We've all written code like this, and we know the risks. There are definitely scenarios where this kind of solution will be used, and where it is the right choice. Just not many of them. – david.pfx Feb 01 '14 at 14:38
1

How about using multiple inheritance like this:

class CNested {
public: 
    virtual void operator=(int i) { SomeMethod(i); }
    virtual void SomeMethod(int i) = 0;
};

class CEnclosing: public CSomeGeneric, public CNested {
    int nEncMember;
public:
    CNested& nested;
    CEnclosing() : nested(*this), nEncMember(456) {}
    virtual void SomeMethod(int i) { std:cout << i + nEncMember; }
};
DUC HOANG
  • 11
  • 1