-1

I'm storing my game's states (collections of entities, essentially) in a vector of shared pointers. When adding states to the vector, the derived part of the states is lost and they revert to the base state class. It all compiles fine, but when I query the states' names, they all come back as DEFAULT_STATE_NAME. I've read plenty of information about object splitting, but I cannot see what is going wrong here.

State.hpp

class State {

protected:

    Game &game;

public:

    typedef shared_ptr<State> Pointer;

    static const StateName name = DEFAULT_STATE_NAME;

    explicit State(Game &game_) : game(game_) ;

    virtual ~State() {}

};

Example derived state class

namespace {

class Overworld : public State {

public:

    static const StateName name;

    Overworld(Game &game) : State(game) {}

};

const StateName Overworld::name = OVERWORLD;

}

Game.hpp

class Game {

private:

    vector<State::Pointer> states;

public:

    void addState(const State::Pointer &state) {
        if(!state)
            throw "invalid state error";

        states.push_back(state);
    }

    // ...

}
Deduplicator
  • 44,692
  • 7
  • 66
  • 118
Inigo Selwood
  • 822
  • 9
  • 20
  • 1
    The problem has nothing to do with object splitting. It's the fundamental way in which runtime polymorphism works in C++. You can ofcourse cast a reference to a reference of some derived object, which you might find `static_cast` and `dynamic_cast` useful for (though I wouldn't recommend doing anything blind, you should make sure that you have a solid understanding of runtime polymorphism). [The Definitive C++ Book Guide and List](https://stackoverflow.com/questions/388242/the-definitive-c-book-guide-and-list) – George Oct 23 '18 at 20:45
  • 3
    Why do you expect `name` to be anything other than the initializer shown here? We do not have enough information to help you here, please provide a [mcve] – KABoissonneault Oct 23 '18 at 20:45
  • 1
    Please **[edit]** your question with an [mcve] or [SSCCE (Short, Self Contained, Correct Example)](http://sscce.org) this exhibits the issue – NathanOliver Oct 23 '18 at 20:46
  • Move `StateName` to be private and squelch any public getter you chose not to post in the minuscule fraction of code you saw fit to post. Anyplace that is accessing it (or the public getter not shown here) will puke all over your compiler output. It should be short work thereafter to find where things went south. – WhozCraig Oct 23 '18 at 20:47
  • 4
    Variables cannot act like `virtual` functions, you could replace it with a `virtual` function that returns the correct name on a per type basis. – George Oct 23 '18 at 20:52
  • move `= ... ` to constructor and use `const_cast` – Алексей Неудачин Oct 23 '18 at 20:57
  • ie `const std::vector b({1}); (*const_cast*>(&b))[0]=2;` – Алексей Неудачин Oct 23 '18 at 21:27

2 Answers2

2

In order to access member methods of a derived class through a pointer (or reference) to its base class, you must use polymorphism (which you didn't). For example

struct Base {
    virtual string name() const { return "Base"; }
};

struct Derived : Base {
    string name() const override { return "Derived"; }
};

const Base*ptr = new Derived;
assert(ptr->name()=="Derived");

Such polymorphism only works with non-static member methods, not with data members nor with static member functions. In your case, there is no polymorphism and hence Base::name remains, well, Base::name.

In your particular case, there are two other possible solutions, though. First, you can use RTTI, though this is generally frowned upon. Another option is to keep the name as a data member in Base and pass it in at construction:

struct Base {
    const string name = "Base";
    Base() = default;
  protected:
    Base(string const&n)
    : name(n) {}
};

struct Derived : Base {
    Derived()
    : Base("Derived") {}
};

const Base*ptr = new Derived;
assert(ptr->name=="Derived");

when there is no polymorphism (and hence no virtual table and additional indirection) involved, but at the cost of a data member name.

Walter
  • 44,150
  • 20
  • 113
  • 196
  • In your first example, you're returning a reference to a temporary string. It's probably a safe bet (AFAIK guaranteed as of C++17) that the copy/move that could be naively invoked when not returning by reference will be elided. – George Oct 23 '18 at 21:21
  • @George Well spotted. Fixed now. – Walter Oct 23 '18 at 21:22
0

name in State and name in Overworld are two completely independent class-variables. They are not part of any instances state, nor can you directly query an instance for class-variables, as those cannot be virtual. In order to Access class-variables polymorphically, you need to use a virtual function.

Add such a member-function to State, and don't forget overriding it in derived classes as needed. Or, you know, you could just use the languages standard RTTI using typeid.

Deduplicator
  • 44,692
  • 7
  • 66
  • 118