Search code examples
c++classcomposition

Is there a way to add references to C++ class members to a vector without explicitly adding them?


I'm trying to determine what pattern(s) may be used to declare a C++ class in a more "compositional" and less "imperative" way.

I'm trying to write a class that includes multiple MyDescriptor members. The MyDescriptor needs to be initialized by the MyHost classes constructor and it iterates over references to the MyHost members to do this.

Is there a way to declare MyHost implementations that don't require adding class members references to a container separately?

class MyDescriptor {
public:
  string name;
  string fqn;
  MyHost(string n) : name(n) {}
  void init(string host) {
    fqn = host + ":" + name;
  }
  void do_thing() {
    // subclasses to special things.
  }

}

class MyHost {
public:
  vector<MyDescriptor*> descriptors;
  string name
  MyHost(string n, vector<MyDescriptor*> d) : name(n),descriptors(d) {
    for (MyDescriptor *d : descriptors) {
      d->init(name);
    }
  }
}

MyHostImpl : public MyHost {
public:
  // descriptors must be accessible as members like this
  MyDescriptor d_1 = MyDescriptor("abc");
  MyDescriptor d_2 = MyDescriptor("123");

  MyHostImpl(string n) : MyHost(n, {&d_1, &d_2}) {} // This is the issue

  void do_thing_1() {
    // UPDATE: This is loose example - but what is important to
    // know is that developers need to access / use descriptors
    // in methods like this.
    d_1.do_thing();
  }

}

I'd ideally like a way to stop declaring the descriptors items explicitly; this {&d_1, &d_2} is what I'd like to eliminate. My team uses a similar pattern and are constantly frustrated by accidentally not adding to descriptors to the vector after adding it to the class.


Solution

  • Invert the ctor semantics. Instead of host ctor accepting descriptor pointers, Have descriptor's ctor take a host reference and add itself to to the host vector:

        MyDescriptor::MyDescriptor(MyDescriptor const&)=delete;
        auto& MyDescriptor::operator=(MyDescriptor const&)=delete;
        MyDescriptor::MyDescriptor(string n, MyHost& h): name{n},fqn{n+":"+h.get_name()}
        {
            h.add(this);
        };
    
        MyHost::MyHost(string n):name{n}{};
        void MyHost::add(MyDescriptor* d){
            descriptors.push_back(d);
        };
        auto& MyHost::get_name()const{
            return name;
        };
    

    This way you can not forget to add descriptors to the host or the compiler cries.

    MyHostImpl::MyHostImpl(string n):
        MyHost{n},
        d_1{"abc",*this},/*compiler kills you if you forget this line*/
        d_2{"123",*this}/*compiler kills you if you forget this line*/
    {};