I am a little lost on the proper way to hold a data structure of multiple derived classes. I am relatively new to C++, so sorry if I have idiotic errors/misconceptions.
At the moment, I use a vector<Base*>*
, which makes sense to me. However, I run into some issues when I try to use the objects contained in the vector.
I am going to use this sample set up to showcase my issues:
Base.h
class Base {
public:
Base();
Base(int a, int b);
virtual ~Base();
friend std::ostream& operator<<(std::ostream& os, Base& base);
int getA();
int getB();
protected:
int a;
int b;
};
Derived.h
class Derived : public Base {
public:
Derived(int a, int b, int c);
~Derived();
friend std::ostream& operator<<(std::ostream& os, Derived& derived);
getC();
private:
int c;
};
int main() {
vector<Base*>* objects = new vector<Base*>();
Base* newbase = new Base(0, 1);
Derived newderived = new Derived(2, 3, 4);
objects.push_back(newbase);
objects.push_back(newderived);
cout << objects->front()->getA() << endl; // '0'
cout << objects->back()->getA() << endl; // '2'
cout << objects->back()->getC() << endl; // error: 'class Base' has no member named 'getC()'
}
Despite the last object in objects
being an instance of class Derived
, it is recognized only as class Base
. This of course makes perfect sense; objects
hold Base*
.
While I understand why the error is occurring, I'm confused on how to solve it. In my previous searches, I found that there were two commonly proposed (and also rather debated) solutions: including every member function my different derived classes use as a virtual
function in Base
and using static casting.
What is the proper method/good practice for using member functions of derived classes inside of data structures containing Base*
?
int main() {
vector<Base*>* objects = new vector<Base*>();
Base* newbase = new Base(0, 1);
Derived newderived = new Derived(2, 3, 4);
objects.push_back(newbase);
objects.push_back(newderived);
cout << *(objects->front()) << endl; // Executed `Base`'s overloaded insertion operator
cout << *(objects->back()) << endl; // Also executed `Base`'s overloaded insertion operator
}
In this case, I found very little reliable suggestions for solving this problem, other than "just use a print() method instead." I understand that I could work around this issue, but I would rather understand the proper way to implement something than just avoid using it.
So, is it possible to somehow execute the derived class' overloaded insertion operator when called from a vector<Base*>
? Any friend function?
int main() {
vector<Base*>* objects = new vector<Base*>();
Derived newderived = new Derived(2, 3, 4);
Derived2 newderived2 = new Derived2(5, 6, 7, 8);
objects.push_back(newderived);
objects.push_back(newderived2);
if (/* objects->front() is a Derived */) cout << "Type check success << endl;
if (/* objects->back() is a Derived2 */) cout << "Type check success << endl;
}
This problem, specifically, has been addressed several times before. The two solutions I have seen is somehow evaluating what occurs after a static cast and by storing some form of type list in the base class and storing a type value in all the derived classes.
However, as I mentioned before, I am new to C++, and I don't understand the former option or the proper way to implement the second. What is the proper way to solve this problem, and are there any explained example videos or the like for me to investigate?
I know this is a bit of a long question, but it seemed to me that it was all so closely interconnected that I should keep the three sub-questions together.
Thanks for the help!
IMO all of your problems stem from trying to fight against your chosen solution - dynamic polymorphism.
That technique works best when every derived type shares a common interface. The whole point of dynamic polymorphism is that the calling code does not need to know or care what the actual type is, it only cares that it uses a specific interface.
In your example:
1) You are trying to use two different interfaces. Your dynamic
polymorphism is acting through the Base
interface and you want to use the Derived
interface. With dynamic polymorphism pick your interface. If you need to know the specific type in a given part of the system then polymorphism is simply not the best solution in that area of the code.
2) The proper way to do that is to add a print()
function. The idea is that every derived type knows how to print itself so that the calling code doesn't need to know or care how that is done.
3) In parts of the system where dynamic polymorphism fails (where you need to know the concrete type of the object) then the recommended way to discover the type is using a dynamic_cast
:
if(auto derived = dynamic_cast<Derived*>(objects->front()))
{
derived->getC(); // Derived specific calls
}
If the pointer is of the wrong type a nullptr
is returned and the if()
fails.
What I have found with C++
is that dynamic polymorphism is really not the best solution for a lot of problems. There can be a temptation to try to make everything dynamically polymorphic but IMO that only works well for a given subset of object oriented design problems.
C++
also excels in other areas such as static polymorphism (using the template engine) and procedural polymorphism (function overloading). These are well worth exploring too.