简体   繁体   中英

Avoiding object-slicing in vector<shared_ptr<Base>>

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);
    }

    // ...

}

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 .

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 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM