避免向量中的对象切片<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
中的name
和Overworld
中的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
。
我将我的游戏状态(基本上是实体集合)存储在一个共享指针向量中。向向量添加状态时,状态的派生部分会丢失,它们会恢复为基本状态 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
中的name
和Overworld
中的name
是两个完全独立的class-变量。它们不是任何实例状态的一部分,您也不能直接查询实例的 class 变量,因为它们不能是 virtual
。为了以多态方式访问 class-变量,您需要使用虚函数。
将这样的成员函数添加到 State
,并且不要忘记根据需要在派生的 class 中覆盖它。或者,您知道,您可以使用 typeid
.
为了通过指向其基础 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
。