避免在vector <shared_ptr <base >>中进行对象切片

时间:2018-10-23 20:39:02

标签: c++ pointers rtti class-variables object-slicing

我将游戏状态(本质上是实体集合)存储在共享指针的向量中。将状态添加到向量中时,状态的派生部分将丢失,它们将还原为基本状态类。一切都可以编译,但是当我查询州名时,它们都以DEFAULT_STATE_NAME的形式返回。我已经阅读了很多有关对象拆分的信息,但是我看不到这里出了什么问题。

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() {}

};

派生状态类示例

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

    // ...

}

2 个答案:

答案 0 :(得分:2)

要通过指向基类的指针(或引用)访问派生类的成员方法,您必须必须使用多态性(不是) 。例如

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

这种多态性仅适用于非静态成员方法,不适用于数据成员或静态成员函数。在您的情况下,没有多态性,因此Base::name仍然是Base::name

在您的特定情况下,还有其他两种可能的解决方案。首先,您可以使用RTTI,尽管通常对此并不满意。另一种选择是将name保留为Base中的数据成员并在构造时将其传递:

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

不涉及多态性(因此不涉及虚拟表和其他间接访问)时,但以数据成员name为代价。

答案 1 :(得分:0)

name中的

Statename中的Overworld是两个完全独立的类变量。它们不是任何实例状态的一部分,也不能直接向实例查询类变量,因为它们不能为virtual。为了多态访问类变量,您需要使用虚函数。

将这样的成员函数添加到State中,不要忘记根据需要在派生类中重写它。或者,您知道,您可以只使用typeid语言标准的RTTI。