C++中的数据继承

data inheritance in C++

本文关键字:继承 数据 C++      更新时间:2023-10-16

我有两个类,一个用于存储基本数据,另一个用于存储其他数据,如下所示:

struct AnimationState(){
virtual ~ AnimationState(){};
Vector3f m_spacialData;
float m_fTimeStamp;
}

和派生类:

struct HermiteAnimationState() : public AnimationState{
virtual ~HermiteAnimationState(){};
Vector3f m_tangentIn;
Vector3f m_tangentOut;
}

我的问题是:首先,我如何创建一个HermiteAnimationState的实例,然后将其转换为AnimationState以存储在如下所示的向量中:

std::vector<AnimationState>     m_vStates;
...

最近,我可以获取对象 AnimationState 并将其向下转换为HermiteAnimationState以访问其他数据(成员 m_tangentIn 和 m_tangentOut)。

HermiteAnimationState* p = dynamic_cast<HermiteAnimationState*>(&m_vStates[i])

多态性在C++中的工作方式是,如果B是基类并且D是从B派生的,则:

  • 指向D的指针可用于需要指向B的指针
  • D的引用可用于预期对B的引用

C++中不能做的实际上是在需要B类型的的上下文中使用类型D的值。例如,不能将派生对象存储在基对象数组中。当您认为派生对象可能与基对象具有不同的大小时,这是有意义的。

同样,不能将派生对象存储在基对象的向量中。

您可以做的是将指向HermiteAnimationState指针存储在指向AnimationState指针向量中。如何管理内存取决于您。例如,以下内容将有效:

std::vector<AnimationState*> m_vStates;
HermiteAnimationState h_a_s;
m_vStates.push_back(&h_a_s);

HermiteAnimationState* p = dynamic_cast<HermiteAnimationState*>(m_vStates[i])

由于h_a_s是一个局部变量,因此它将在其作用域结束时自动销毁。

这可能是一种行不通的方法,因为您可能希望向量元素引用的对象持续到当前范围之外。我们可以为此目的使用std::unique_ptrstd::unique_ptr拥有它指向的对象,只要它保持活动状态,该对象也是如此;并在对象本身被销毁时将其删除。因此,std::unique_ptr对象的向量在内存管理方面的行为类似于对象本身的向量。现在你可以做

std::vector<std::unique_ptr<AnimationState*>> m_vStates;
m_vStates.emplace_back(new HermiteAnimationState);

HermiteAnimationState* p =
dynamic_cast<HermiteAnimationState*>(m_vStates[i].get());

(但请注意,您无法复制此向量;只能移动它。

基本上,您需要对指向的对象使用某种引用,因为您需要动态多态性。

最简单但容易出错的是使用"裸"指针。首先有问题的是你必须手动进行销毁:容器将销毁指针,而不是指向的内容。

执行此操作更安全的方法是使用智能指针,这些指针旨在根据智能指针嵌入其类型的预先固定的规则进行销毁。如果你怀疑的话,最简单的一个,当然也是最好的选择是std::unique_ptr,它不能复制,但可以移动。另一个选择,在使用之前应该仔细考虑,是std::shared_ptr这是有用的IFF,你不知道什么时候应该销毁这些对象,但你知道这是一些系统不再引用它的时候。其他一些系统可能只是在观察该对象,在这种情况下 std::weak_ptr。


现在,通过阅读您的问题,我认为您肯定正在处理很多这些动画数据。那里有一个明显的设计问题,我想,我可能是错的。 但是,看起来,如果你有很多这样的AnimationState要管理,在一个循环中,你会遇到性能问题。这是游戏中的常见问题,主要是由"缓存一致性"引起的。

在这种情况下,我会建议不要使用

  • 继承:它邀请CPU到处跳转并触发缓存未命中;
  • dynamic_cast:这是少数几个不能保证在可预测的时间内结束的操作之一(例如,使用 new 和 delete),这基本上意味着如果您处于关键循环中,您可能会为此浪费大量时间。在某些情况下,您无法避免使用动态强制转换(例如在执行动态插件时),但在大多数情况下,仅仅因为您选择使用继承而使用它是错误的。如果使用继承,则应使用虚拟调用。

然而,我的建议更加激烈:根本不使用继承。

显然,这只是一个建议。如果你没有做一些关键循环的事情,那也没关系。我只是担心,因为看起来您正在为组合做一些继承,这总是对代码的可读性和性能产生不良后果。