Referring to an object of a derived class from the base class of another (unrelated!) Class in C ++

If you cannot understand the name of the question from the very beginning, it is not your fault - I could not come up with a better description. Here is an explanation of the problem, which may be a little long, so we apologize in advance.

In the original version of my program, I had an Ecosystem class and an individual class:

// Very simplified, for illustration purposes class Ecosystem { protected: // The int is just the ID of the individual. std::map<int, std::shared_ptr<Individual> > individuals; public: Ecosystem(); void func(int _individual_id) { std::cout << "Individual age: " << individuals[_individual_id]->get_age() << std::endl; } void routine(int _individual_id) { // Another function working via // the pointers in individuals. } // More such functions... }; class Individual { protected: int age; public: Individual(); inline int get_age() const { return age; } }; 

The Ecosystem class contains dozens of functions, and I will add much more in the future.

Now I decided to divide the Individual class into a base class and two derived classes, for example TypeAIndividual and TypeBIndividual, because each of them has members and attributes that the other does not need (they also have several members and attributes through the base class). Therefore, I have a base individual class and two derived classes:

 class TypeAIndividual : public Individual { protected: // Data structures specific to individuals of type A public: TypeAIndividual(); }; class TypeBIndividual : public Individual { protected: // Data structures specific to individuals of type B public: TypeBIndividual(); }; 

The problem is that the ecosystem also needs to be divided into TypeAEcosystem and TypeBEcosystem:

 class Ecosystem { protected: // Holding pointers to the base Individual class is pointless (pun not intended) // std::map<int, std::shared_ptr<Individual> > individuals; public: Ecosystem(); // I want to keep func() in the base class // because it only accesses attributes and // members common to both classes derived // from Individual. void func(int _individual_id) { // Hmmmm... // The pointers don't live in the Ecosystem class any more! std::cout << "Individual age: " << individuals[_individual_id]->get_age() << std::endl; } // OK to implement in each class // derived from Ecosystem. virtual void routine(int _individual_id) = 0; }; class TypeAEcosystem : public Ecosystem { protected: // Pointers to individuals // of the corresponding type. std::map<int, std::shared_ptr<TypeAIndividual> > individuals; public: TypeAEcosystem(); // Reimplementing routine() is OK // because it does things specific to // this individual type. virtual void routine (int _individual_id) { // Operate on data structures particular // to this type of individual. } }; class TypeBEcosystem : public Ecosystem { protected: // Pointers to individuals // of the corresponding type. std::map<int, std::shared_ptr<TypeBIndividual> > individuals; public: TypeBEcosystem(); // Reimplementing routine() is OK // because it does things specific to // this individual type. virtual void routine (int _individual_id) { // Operate on data structures particular // to this type of individual. } }; 

TypeAEcosystem and TypeBEcosystem use void func(int _individual_id) , which need access to individuals of the appropriate type. But the base class Ecosystem no longer contains pointers to people, since std::map are in every derived class, and not in the base class.

My question is: how can I access the appropriate personality type ( TypeAIndividual or TypeBIndividual ), while avoiding the implementation of a separate void func(int _individual_id) in each class derived from Ecosystem? In other words, is there a way to save func() in the base class so that when I change it, I don’t need to make changes to the derived classes? In a real program, there are dozens of functions, such as func() , that take only int as a parameter. In addition, some of these functions accept individual identifiers from other structures of the Ecosystem class, so I cannot just pass a pointer to TypeAIndividual or TypeBIndividual .

Things I Considered

  • Combining TypeAIndividual and TypeBIndividual back into the generic Individual class with all the data structures needed for both derived classes. It strikes me as a particularly clumsy way to do something, but at least it will work.

  • Creating virtual objects func() and Co. and their implementation in TypeAEcosystem and TypeBEcosystem . This means that if I want to make changes to any of the functions, I must change both implementations (= service nightmare).

  • Having only one Ecosystem class that contains std::map two types of individuals, for example:

     // Seems clunky... class Ecosystem { protected: // Note: The Ecosystem can contain // one OR the other, but not both! // One map will always be empty. std::map<int, std::shared_ptr<TypeAIndividual> > type_a_individuals; std::map<int, std::shared_ptr<TypeBIndividual> > type_b_individuals; public: Ecosystem(); void func(int _individual_id) { // Check what type of individuals we // are working with and operate on the // appropriate container. if (type_a_individuals.size() > 0) { std::cout << "Individual age: " << type_a_individuals[_individual_id]->get_age() << std::endl; } else { std::cout << "Individual age: " << type_b_individuals[_individual_id]->get_age() << std::endl; } } }; 

This will require inserting a check into each function, which is almost as bad in terms of maintainability as the presence of functions in individual classes.

Note Although I would very much like to avoid the omissions of pointers, I would consider raising and / or lowering the rating (in the worst case ...) if it solves the problem.

Any suggestions are welcome!


Change 1

Thanks everyone for the fantastic answers! As amit and Chris suggested, both looked at my Ecosystem class and, of course, it was too cumbersome. I have moved member functions to other classes, and now I have up to four or five core functions of the Ecosystem class. The Ecosystem class is located in the library and provides an interface for experimenting with individuals, but I do not want users to be able to directly manipulate Individual and other classes, so I cannot completely abandon it.

I liked all the suggestions, there are several ingenious solutions. However, the one Chris suggested immediately drew my attention to the fact that he was very neat and allowed me to have one Ecosystem class, and not three separate classes (base and two derivatives). The type of individual can be specified in the configuration file, and I can spawn several ecosystems from different configuration files within the same experiment. This is an accepted answer.

Thanks again for the constructive contribution!

0
source share
4 answers

As I said in my comment, you might consider making Ecosystem a class template and have one Ecosystem instance for each IndivualType.

 template <class IndivualType> class Ecosystem { protected: // The int is just the ID of the individual. std::map<int, std::shared_ptr<IndivualType> > individuals; public: // ... }; 

If you need an Ecosystem to behave differently for a given individual type, you can also explicitly specialize your ecosystem as follows:

 template <> class Ecosystem<SpecialIndividualType> { protected: // The int is just the ID of the individual. std::map<int, std::shared_ptr<SpecialIndividualType> > individuals; public: // special implementation for EcoSystem for SpecialIndividualType }; 

This is probably not needed, however it may be useful to know.

Finally, as you said,

  The Ecosystem class contains dozens of functions, and I will add a lot more in the future. 

You might consider dividing the functionality of your ecosystem into policies. I do not know your needs, but as an example:

 template <class IndivualType, class SomePolicy1, class SomePolicy2> class Ecosystem { private: const SomePolicy1 mSp1; const SomePolicy2 mSp2; protected: // The int is just the ID of the individual. std::map<int, std::shared_ptr<IndivualType> > individuals; public: Ecosystem (const SomePolicy1& sp1= SomePolicy1(), const SomePolicy2& sp2= SomePolicy2())) : mSp1(sp1), mSp2(sp2) {} // ... void func(int _individual_id) mSp1.doSmth(_individual_id); } void func2(int _individual_id) { mSp2.doSmth(_individual_id); } }; 

This is called a "policy-based design", you can find a lot of information about it on the Internet.

Of course, there are other solutions, such as creating virtual methods, as already mentioned. I would probably try both (depending on what you have) and see what you feel most comfortable with.

0
source

Looking at implementation details, I guess this. An ecosystem is a container / processor class. Looking at the interface, identifiers seem unique to different people, i.e. Individual B cannot have the same identifier. If they are correct, I will adhere to one class of ecosystem that defines an interface for access to individuals, which can be stored on the map (basic pointers *), since identifiers are unique. Then you can use dynamic casting from the outside if you want to know what type is being requested, and other manipulations can be performed using the polymorphic interface of individual classes. I also highly recommend that you use the implementation proposed by Herb Sutter http://www.ezinearticles2.com/publications/mill18.htm#Notes , because many experts highly appreciate its extensibility among others.

0
source

You can add a virtual method to EcoSystem to get a generic TypeIndividual :

 class EcoSystem { public: void func(int _individual_id) { std::cout << "Individual age: " << get_individual(_individual_id).get_age() << std::endl; } virtual const TypeIndividual& get_individual(int _individual_id) const = 0; virtual void routine(int _individual_id) = 0; }; 

And for each subclass:

 class TypeAEcosystem : public Ecosystem { protected: // Pointers to individuals of the corresponding type. std::map<int, std::shared_ptr<TypeAIndividual> > individuals; public: const TypeIndividual& get_individual(int _individual_id) const override { return *individuals.at(_id); } // Reimplementing routine() is OK // because it does things specific to // this individual type. void routine (int _individual_id) override { // Operate on data structures particular // to this type of individual. } }; 
0
source

Both derivatives of Ecosystem are distinguished by their way of storing and accessing Individual . This is a good example for creating Individual virtual access behavior. With a return type covariance dash, this looks pretty good:

 struct Individual { void baseStuff() {} }; struct TypeAIndividual : Individual { void typeAStuff() {} }; struct Ecosystem { void func(int id) { individual(id).baseStuff(); } virtual void routine(int id) = 0; protected: virtual Individual &individual(int id) = 0; }; struct TypeAEcosystem : Ecosystem { TypeAIndividual &individual(int id) override { return *_individuals.at(id); } void routine(int id) override { individual(id).typeAStuff(); } private: std::map<int, std::shared_ptr<TypeAIndividual>> _individuals; }; 

Since the card and its accessor (s) are identical, except for the type of individual, you can decompose them into the base class of the intermediate template:

 template <class Individual> struct DerivedEcosystem : Ecosystem { Individual &individual(int id) override { return *_individuals.at(id); } private: std::map<int, std::shared_ptr<Individual>> _individuals; }; struct TypeAEcosystem : DerivedEcosystem<TypeAIndividual> { void routine(int id) override { individual(id).typeAStuff(); } }; 
0
source

All Articles