【问题标题】:Base class of non polymorphic derived classes非多态派生类的基类
【发布时间】:2014-12-04 10:40:56
【问题描述】:

我有以下类定义:

class BaseHandle { /* Lots of things */ };
class VertexHandle : public BaseHandle {
    /* Only static members and non-virtual functions, default dtor */ };
class EdgeHandle : public BaseHandle { /* Dito */ };
class FaceHandle : public BaseHandle { /* Dito */ };

所有类都没有虚函数或基。
派生类只派生自BaseHandle,不添加任何非静态成员,也不添加非默认dtor。

我想将Vertex-Edge-FaceHandles保存在同一个向量中:

std::vector<BaseHandle*> handles;

但它不起作用,如果我检索 BaseHandle 对象并想将它们 dynamic_cast 到派生对象它会失败,因为类不是多态的(这是我的解释,也许我错了)。

如何实现BaseHandles 的公共向量?我应该提一下,我不能更改类定义,因为它们是第三方库的一部分。

【问题讨论】:

  • 要使多态类工作,您至少需要 一个 虚函数:析构函数。
  • 您可以创建自己的并行类层次结构并将这些类型中的每一个添加为成员。
  • 您的问题有解决方案。使用虚函数。 typeidstatic_cast 的组合属于问题域而不是解决方案域。您正在重新发明旧的疲惫的class BaseHandle { int TYPE; ... }; switch (TYPE) { ... 成语,众所周知,这是一个巨大的混乱。
  • 是否有任何派生类有额外的成员,分别比BaseHandle 更多的基类?他们中的任何一个都有用户定义的 dtors 吗?
  • 如果您必须dynamic_cast 某些东西才能使用它,这通常表示设计不佳。我怀疑这些对象是否属于同一个vector

标签: c++


【解决方案1】:

你需要在你的父类中有一个虚拟析构函数才能多态使用它

class BaseHandle 
{
  public:
    virtual ~BaseHandle();

  ...

};

这是因为dynamic_cast 与 RTTI(运行时类型信息)一起使用,只有当你的类至少有一个虚拟成员函数时才可用

这也将防止资源泄漏,否则只会破坏您实例的父类部分


解决方法

您可以使用std::shared_ptr 中的std::vector,不仅不必手动调用newdelete 来避免内存泄漏,而且智能指针还有一个神奇的属性(它存储删除器根据构造方式调用破坏)来解决您的问题:

int main()
{
  std::vector<std::shared_ptr<BaseHandle>>      shared_vec;

  shared_vec.push_back(std::make_shared<VertexHandle>());

} // At the end of scope all destructors are called correctly

如果您无法访问 c++11,您可以使用 boost::shared_ptr

【讨论】:

  • 我应该提一下,我不能更改类定义,因为它们是第三方库的一部分。
  • @TerenceChill 那么这些类并非设计用于多态性:) 只有 hacks 和变通方法才有效
  • @TerenceChill 我添加了一个不那么可怕的解决方法
  • 任何虚函数或基数都足以多态地使用它,虚拟 dtor 只需要多态地删除它。虽然如果一个人有任何虚拟成员或基地,让 dtor 也成为虚拟只是常识......
  • 我认为基类必须是多态的。你确定让派生类多态就够了吗?
【解决方案2】:

你可以存储

struct thing
{
    enum Type { vertex, edge, face };
    Type type;
    union
    {
        VertexHandle * vh;
        EdgeHandle * eh;
        FaceHandle * fh;
    };
};

但这基本上是一团糟……你确定要这样做吗?看起来您将多种类型存储在一个数组中,尽管事实上无法以多态方式使用它们,那么实际上是否有充分的理由只有一个数组而不是三个?

【讨论】:

  • 我想把所有句柄放在一个空间数据结构中。所以我可以问,哪些顶点、边和面在边界框内。数据结构的查询结果为BaseHandles。通过投射BaseHandle 我会知道它是顶点、边还是面。一个解决方案是拥有三个空间数据结构,但这很难看。
  • 很公平。不幸的是,如果没有多态性,你在这里所做的一切都会很丑陋,我认为三个独立的数组可能是最不丑陋的。
【解决方案3】:

根据 Kerrek 的评论。您可以“创建自己的并行类层次结构并将这些类型中的每一个添加为成员”。例如:

class MyBaseHandle {
 public:
  virtual ~MyBaseHandle(){}
  virtual Box getBoundingBox() const = 0;
};

class MyEdgeHandle : public MyBaseHandle {
  std::unique_ptr<EdgeHandle> handle_;
 public:
  MyHandle(std::unique_ptr<EdgeHandle> handle) : handle_(std::move(handle)) {}
  Box getBoundingBox() const override;
};

如果你愿意,你可以dynamic_cast。但我会尽量避免使用dynamic_cast。在并行类层次结构中添加 virtual 方法来满足您的需求。例如,我在基类中添加了一个 virtual getBoundingBox 函数,然后您可以专门针对您的特定类型的句柄:

Box MyEdgeHandle::getBoundingBox() const {

  // Get data from EdgeHandle
  auto v1 = handle_->getVertex1();
  auto v2 = handle_->getVertex2();

  // create box from edge data...

  return box;
} 

Live demo

【讨论】:

    【解决方案4】:

    如果从BaseHandle 派生的所有类仅使用来自BaseHandle 的单一继承(加上可能从具有琐碎dtor 的空类继承,这些类需要进行空基类优化)并且除了非- virtual 函数和static 成员,并且所有派生类都使用默认的dtor 或等效项,您只需static_cast 即可。

    但请注意,无法知道实际上是哪个派生类(如果有的话)。

    【讨论】:

    • 如果我不知道BaseHandle 指向哪个派生类,我怎么能static_castBaseHandle?没有虚函数意味着没有 rtti,所以我不能使用typeid 然后进行强制转换,对吧?
    • 对。您只需要从BaseHandles 成员那里知道带外知识或其他什么。如果它很重要,它可能不会。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-08-16
    相关资源
    最近更新 更多