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.
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*/
{};