避免向量中的对象切片<shared_ptr<Base>>

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

我将我的游戏状态(基本上是实体集合)存储在一个共享指针向量中。向向量添加状态时,状态的派生部分会丢失,它们会恢复为基本状态 class。一切都编译得很好,但是当我查询州名时,它们都返回为 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() {}

};

示例派生状态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);
    }

    // ...

}
State中的

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

将这样的成员函数添加到 State,并且不要忘记根据需要在派生的 class 中覆盖它。或者,您知道,您可以使用 typeid.

语言标准 RTTI

为了通过指向其基础 class 的指针(或引用)访问派生 class 的成员 方法 ,您 必须使用多态性(你没有)。例如

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

当不涉及多态性(因此不涉及虚拟 table 和其他间接寻址),但以数据成员为代价时 name