-1

I'm trying to have a vector of different subclass pointers that have a common base class. The vector is set to the base class pointer but anything that is added to the vector doesn't get the full functionality of the subclass it is. It can be seen in the error log it is being treated as a base class so not getting the extended functionality.

I've looked on loads of questions and people are saying to do it the way I am doing it, but for whatever reason, it's not working.

The code is on a public repo.it:

https://repl.it/@cubingminer8/inheritance-with-vectors-testing

Any help would be greatly appreciated!

edit: ok so I'm going to use this for a sprite group system in a c++ sdl2 game engine. There will be a base sprite class that has some basic things like render and move, while any sprites I need will be their own classes that inherit from Sprite, they will have their own unique behaviors so virtual functions would be impractical. There will be a sprite group object, that objects that inherit from Sprite can be stored in. So they can all be rendered at once and such.

If you have ever used pygame then it is almost identical to the sprite and spritegroup system used there. https://www.pygame.org/docs/tut/SpriteIntro.html

#include <iostream>
#include <vector>

class Base {
public:
    char A = 'A';
};

class Sub : public Base {
public:
    char B = 'B';
};

class Sub2 : public Base {
public:
    char C = 'C';
};

int main() {
    std::vector<Base*> List;
    List.push_back(new Sub());
    List.push_back(new Sub2());

    std::cout << List[0]->B << std::endl; // it should be able to print B
    std::cout << List[1]->C << std::endl; // but it is being set as a base class and
                                          // not getting the functionality of the subclass it is.
}
JeJo
  • 30,635
  • 6
  • 49
  • 88
  • 3
    *people are saying to do it the way I am doing it* Alas, they are wrong. – n. m. could be an AI Dec 19 '18 at 16:31
  • It's very much possibly to safely do what you're trying to do without ugly `reinterpret_cast`s, and the language offers a variety of tools to do this. One is [`virtual` methods](https://stackoverflow.com/questions/2391679/why-do-we-need-virtual-functions-in-c). But this question would be better answerable if you mentioned if you told us _what_ you want to achieve, being as general as possible. – alter_igel Dec 19 '18 at 16:33
  • Handy supplementary reading since you are learning inheritance: [The Lyskov Substitution Principle.](https://stackoverflow.com/questions/56860/what-is-an-example-of-the-liskov-substitution-principle) – user4581301 Dec 19 '18 at 17:50

2 Answers2

5

Usually, this is achieved by virtual functions. In the given case it should be a virtual getter function which returns the char members of each class.

class Base {
    char A = 'A';
public:
    virtual char getChar()const /*noexcept*/ { return A; }
    virtual Base () = default;
};

class Sub : public Base {
    char B = 'B';
public:
    char getChar()const /*noexcept*/ override { return B; }
};

class Sub2 : public Base {
    char C = 'C';
public:
    char getChar()const /*noexcept*/ override { return C; }
};

now in the main

std::cout << List[0]->getChar() << std::endl;

As a side note, I suggest you to have a look at smart pointers, instead of the row pointers, by which you can avoid manual memory management.

A good starting would be:

#include <memory>

std::vector<std::unique_ptr<Base>> List;
List.emplace_back(std::make_unique<Sub>());
JeJo
  • 30,635
  • 6
  • 49
  • 88
  • If you want, you can discard the `virtual`s on the derived class overrides. once a function becomes virtual it stays virtual – user4581301 Dec 19 '18 at 16:38
  • 2
    And the base class should have a virtual destructor. Also make sure that you delete the objects in the vector after done with it, because that is not performed by the vector destructor thus you'll have memory leaks otherwise. – EmDroid Dec 19 '18 at 16:40
  • @axalis added the virtual destructor; that was a good point. I have made a suggestion for smart pointers, and of course, your comment should direct the OP right path. ;) – JeJo Dec 19 '18 at 16:45
3

So you want this to work:

// your code version 1
std::cout<< List[0]->B << std::endl; //it should be able to print B            
std::cout<< List[1]->C << std::endl; //but it is being set as a base class 

But what should happen if you write this instead?

// your code version 2
std::cout<< List[0]->C << std::endl;
std::cout<< List[1]->B << std::endl;

List[0] doesn't have any C and List[1] doesn't have any B. How do you propose to treat this code?

There are several ways to approach this.

  1. The compiler should know at compilation time that version 1 is right, and version 2 is wrong. Unfortuantely this is generally impossible because the compiler cannot keep track of what object pointer goes to which slot in the array. So this has to be dismissed.
  2. The run time system should detect the error at run time. This is a possible approach, but not one taken by C++. C++ is a statically typed language. Dynamically typed languages can handle this case. If you want a dynamically typed language, try e.g. Python.
  3. The compiler should not try to detect anything, and the runtime system should not try to detect anything either, but go ahead and perforrm the operation anyway, and let it produce wrong results or crash. This is also a possible approach, but not one taken by any modern high-level programming language. C++ and other modern languages are typed. It is possible to circumvent the type system of C++ by using reinterpret_cast and the like, but this is very dangerous and is not recommended.
  4. The compiler should treat both versions as wrong. This is what C++ does.

As others have mentioned, the (only) right way to extend functionality of a class is via virtual functions. This requires some planning ahead. The base should at least declare which operations are needed, though it doesn't need to know how derived classes will implement them.

n. m. could be an AI
  • 112,515
  • 14
  • 128
  • 243