-2

Let's say I have two classes, Parent and Child. Any instances of the Child class must occur as members of a Parent class, and the Child class has, as a data member, a pointer to that Parent. Thus, the Child class has a single constructor that takes said pointer as its sole argument (and there is, by design, no default constructor).

At its simplest, then, we have code like the following (I have included the DoSomething functions so that compilers won't optimize-away the pParent member, which is otherwise unused in this MCVE):

class Parent; // Declaration to enable use of pointer in Child

void DoSomething(Parent* p, int x);

class Child {
private:
    Parent* pParent;
public:
    Child(Parent* pp) : pParent{pp} {}
    // Would like a 'default'-style constructor, like below, where "nullptr" is replaced with a pointer-to-container
//    Child(Parent* pp = nullptr) : pParent{ pp } {}
public:
    void DoSomething(int x) { ::DoSomething(pParent, x); }
};

class Parent {
public:
    Parent() {}
public:
    Child Child1{ this };
//  Child Child2; // error: 'Child': no appropriate default constructor available
};

As I have used in the above code, I can declare/instantiate Child members using the { this } initializer syntax; however, I typically have several thousand such instances in my projects (and there a number of different Child classes), and I was wondering if "Modern C++" (I generally use the C++17 standard) provides any way for the constructor to be declared in a manner similar in form to the commented-out version, but replacing the default argument value (nullptr) with some kind of 'token' (or keyword) that will be converted to a pointer to the 'containing' Parent class at compile-time, and thus hugely reducing the coding burden? Something like this:

Child(Parent* pp = id_of_containing_class) : pParent{ pp } {}

I am still in the process of getting fully 'up-to-speed' with the new language features since C++11, and have not yet found such a tool. But there are many excellent coders here on SO who are far more familiar with what can and cannot be done!

(To provide some context for this: the Child classes represent customized controls in customized dialog-boxes, derived from the Parent class.)

Adrian Mole
  • 49,934
  • 160
  • 51
  • 83
  • Do you often create `Child` instances dynamically? – Botje Dec 12 '19 at 16:17
  • @Botje Child instances are ***only*** ever created when the parent (dialog) is created. This is generally done by either automatic creation (function-local declared, like: `Parent MyBox(args)`) or as a `std::unique_ptr`. Is that what you're asking? – Adrian Mole Dec 12 '19 at 16:20
  • Is this a Huge burden: `struct Parent{Parent():child1{this}{}child child1;};` ? – Aykhan Hagverdili Dec 12 '19 at 16:33
  • @Ayxan It does become rather tedious when I have (say) 30 or 40 child objects in a given derivative of `Parent`. – Adrian Mole Dec 12 '19 at 16:39
  • Any chance of using a singleton to keep a mapping between parents and children? – Mark Ransom Dec 12 '19 at 17:47

2 Answers2

2

If you don't mind a small runtime cost you can store a pointer to the currently-under-construction Parent. The base ParentMarker instance will be created before any children of Parent:

class Parent;

class ParentMarker {
  public:
    thread_local static inline Parent* dynamic_parent = nullptr;
    ParentMarker();
};

class Parent : public ParentMarker {
...
};

ParentMarker::ParentMarker() {
  dynamic_parent = static_cast<Parent *>(this);
}

class Child {
  public:
    Child(Parent * pp = nullptr) 
     : pParent(pp ? pp : ParentMarker::dynamic_parent) {}
    Parent *pParent;
};

Of course this falls apart if you start nesting Parents and creating Children dynamically.

Botje
  • 26,269
  • 3
  • 31
  • 41
2

Is there a Modern C++ way for a constructor to know its 'container' class?

No. It is not possible, in the constructor, to distinguish whether the object is being constructed as a sub-object, or what is the type of the super-object.

Passing this to the child is a good solution for your case.

There is no standard solution, but if you really want default construction to work, that may be possible, if you can use some non-portable tricks. It can only work if the default construction is only ever used when Child is that particular member of Parent. This can be reasonably achieved by making the constructor private, and declaring Parent as friend of Child. Or Child could be a nested class of Parent.

Once we have the guarantee that we are constructing a particular member, there are platform specific macros that give us a pointer to the super object (I got list from here, but looked up originals)

Linux kernel:

#define container_of(ptr, type, member) ({          \
const typeof(((type *)0)->member) * __mptr = (ptr); \
    (type *)((char *)__mptr - offsetof(type, member)); })

That one uses GCC extensions typeof and statement expressions. typeof may be replaced with decltype in C++. offsetof is guaranteed to only work for standard layout types (otherwise conditionally supported since C++17 (probably not supported if there's virtual inheritance), unconditionally UB before that).

Windows (MinGW version)

#define CONTAINING_RECORD(address, type, field) \
  ((type *)((PCHAR)(address) - offsetof(type, field)))

FeeBSD (windows compatibility)

#define CONTAINING_RECORD(addr, type, field)    \
    ((type *)((vm_offset_t)(addr) - (vm_offset_t)(&((type *)0)->field)))

That implementation relies on Child type not having an overloaded addressof operator.

Given any of those, you could use pParent{CONTAINING_RECORD(this, Parent, Child2)}. If the Child is only ever that member, then there would not even be a need to store the pointer in Child; CONTAINING_RECORD is a compile time constant operation.

eerorika
  • 232,697
  • 12
  • 197
  • 326