0

I have two classes, DerivedA and DerivedB that are derived from the same Base class. Both classes hold pointers to an object each, which is of one of two data types, TypeA and TypeB, respectively. Those data types are derived from the same basic type BaseType.

In the Base class, I have an (abstract) getter for those pointers. That getter is overridden in the each of the derived classes and specified such that they should return the derived data types.

In the main function, I define a vector that holds pointers to objects of both derived classes. But when I iterate over that vector, I only get BaseType pointers instead of the expected TypeA/TypeB pointers.

The following MWE illustrates that:

#include <iostream>
#include <vector>

using namespace std;

class BaseType {};
class TypeA : public BaseType {};
class TypeB : public BaseType {};

class Base {
public:
  virtual BaseType* GetObject() = 0;
};

class DerivedA: public Base {
public:
  TypeA* object = new TypeA();
  TypeA* GetObject() override {
    cout << "i'm comming from DerivedA" << endl;
    return object;
  }
};

class DerivedB: public Base {
public:
  TypeB* object = new TypeB();
  TypeB* GetObject() override {
    cout << "i'm comming from DerivedB" << endl;
    return object;
  }
};


int main() {
  vector<Base*> vec = {new DerivedA(), new DerivedB()};
  for (auto* item : vec){
    auto* obj_ptr = item->GetObject();
    cout << "I have an object of type " << typeid(obj_ptr).name() << endl;
  }
}

which yields the following output:

i'm comming from DerivedA
I have an object of type P8BaseType
i'm comming from DerivedB
I have an object of type P8BaseType

Now, I have two questions the answers to which I hope to help me to understand how polymorphism in C++ works:

  1. Why does the call to the GetObject() for each vector item clearly lands in the correct override, but does not return TypeA* or TypeB*, respectively, but a pointer to a BaseType instead?
  2. How would I need to alter the code to get pointers to types TypeA and TypeB, respectively?
E_net4
  • 27,810
  • 13
  • 101
  • 139
Lupino
  • 154
  • 7
  • 7
    1) You need to learn about `virtual` functions, as dynamic polymorphism doesn't exist without it. -- *After a bit of searching and trial and error* -- 2) C++ cannot be learned by trial and error. C++ requires reading and using peer-reviewed materials such as good books and approved websites. – PaulMcKenzie Mar 14 '23 at 08:01
  • 2
    Add the print functionality to the classes themself: https://godbolt.org/z/cvh49Esjj – mch Mar 14 '23 at 08:06
  • @mch damn, my "breakdown" was broken down too much. In my real problem (which involves lists of wrappers for `wxMenu` and `wxMenuItem`), I don't just want to print something, but I want to return the content to process it further. I actually tried your way before but apparently, a virtual method cannot be overridden by methods that return different Types. – Lupino Mar 14 '23 at 08:11
  • @Lupino So the processing also needs to be in virtual methods. That is the way you do the "same thing" for different kinds of data. Or maybe you need a function that returns a string representation of the data in order to process the string further. It is hard to say without knowing the actual problem. – nielsen Mar 14 '23 at 08:15
  • Create function objects that have members, state, etc. The members would serve as the parameters and return type(s). Then overload the call operator, `operator()`. Then it is `operator()` that is called in a loop for each object. Basically you're moving the parameters from the call site to within the function object. – PaulMcKenzie Mar 14 '23 at 08:20
  • Aside from virtual functions (which is the way to go here), there's also a ````std::variant```` type (which goes togehter with ````std::visit````). But don't put the cart before the horse. – nick Mar 14 '23 at 08:21
  • A starting point may be found here: https://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list – nielsen Mar 14 '23 at 08:23
  • @PaulMcKenzie I spent the most part of the past two weeks reading through [this site](https://www.geeksforgeeks.org/c-plus-plus/?ref=shm). Would you consider this an "approved" resource for learning? – Lupino Mar 14 '23 at 11:26
  • 1
    @Lupino: I heard mixed opionions about geeksforgeeks, but no first hand experience. I found learncpp.com to be really helpfull. It starts at the very beginning, so you might skip a few chapters, though. – nick Mar 15 '23 at 07:32
  • 1
    G4G? In two words, stay away. – n. m. could be an AI Mar 22 '23 at 16:43

1 Answers1

2

There are two issues in your sample.

First, BaseType is not polymorphic so typeid will not use RTTI. Adding a virtual function solves this.

class BaseType {
public:
    virtual ~BaseType() = default;
};

Second, typeid inspects the dynamic type of its operand if possible, but does not special-case pointers. obj_ptr itself is unequivocally of type BaseType *, even if it points to a derived object. *obj_ptr, on the other hand, is a BaseType & which typeid(*obj_ptr) will gladly query for its dynamic type.

See the resulting code on Godbolt

Quentin
  • 62,093
  • 7
  • 131
  • 191
  • I don't quite understand why declaring a virtual destructor is working in this case. Is there some rule that states that there has to be at least one method that is set to be "virtual" in the declaration part so that the compiler knows that it is meant to be a polymorphic class? And regarding the second issue: `*` in front of a pointer returns the "real" object that is pointed to; so I don't pass the pointer to `typeid` but the actual object, correct? Or a copy of that object? – Lupino Mar 23 '23 at 05:59
  • 1
    @Lupino that's precisely the rule, yes: a class needs at least one virtual function to become polymorphic, which is to say opt-in to RTTI. Your understanding about `*` is correct as well: the actual object is passed by reference (that's the point of references in the first place, to have a "thing" to pass around to refer to an object without making pointers ambiguous). – Quentin Mar 23 '23 at 11:44
  • 1
    Oh, now i see: I need to make the base *Type* virtual as well, not just the Base *class* that uses this type. This was the thing I was missing. Thanks. – Lupino Mar 23 '23 at 11:58