非多态派生类的基类

6

我有以下类定义:

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派生,并且不添加任何非静态成员,也不添加非默认析构函数。

我想将VertexHandleEdgeHandleFaceHandle保存在同一个向量中:

std::vector<BaseHandle*> handles;

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

我如何实现一个通用的BaseHandles向量?我应该提到,我无法更改类定义,因为它们是第三方库的一部分。


1
为了使多态类起作用,您至少需要一个虚函数:析构函数。 - Some programmer dude
3
你可以创建自己的平行类层次结构,并将每个类型添加为成员。 - Kerrek SB
1
有一个解决方案可以解决你的问题。使用虚函数。typeidstatic_cast的组合属于问题领域,而不是解决方案领域。你正在重新发明旧的、疲惫不堪的class BaseHandle { int TYPE; ... }; switch (TYPE) { ...习语,这被认为是一个巨大的混乱。 - n. m.
1
任何派生类是否具有额外的成员,或者比仅仅继承BaseHandle更多的基类?它们中是否有用户定义的析构函数? - Deduplicator
1
如果在使用对象之前必须进行dynamic_cast,则通常表明设计不佳。我质疑这些对象是否应该放在同一个向量中。 - Chris Drew
显示剩余5条评论
4个回答

7

为了实现多态,您的父类必须拥有一个虚析构函数。

class BaseHandle 
{
  public:
    virtual ~BaseHandle();

  ...

};

那是因为dynamic_cast使用RTTI(运行时类型信息),而只有至少有一个虚成员函数的类才能使用该信息。
这也将防止资源泄漏,否则只会销毁您实例的父类部分。

解决方法

您可以使用一个 std::shared_ptrstd::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
1
@TerenceChill 那些类并不是为了使用多态而设计的 :) 只有一些hack和解决方法才能起作用。 - Drax
1
@TerenceChill 我添加了一个不那么可怕的解决方法。 - Drax
任何虚函数或基类都可以用于多态地使用,虚析构函数仅在多态删除时必要。但是,如果有任何虚成员或基类,将析构函数也设为虚函数只是个常识... - Deduplicator
我认为基类必须是多态的。你确定仅仅使派生类成为多态的就足够了吗? - BЈовић

3
您可以存储
struct thing
{
    enum Type { vertex, edge, face };
    Type type;
    union
    {
        VertexHandle * vh;
        EdgeHandle * eh;
        FaceHandle * fh;
    };
};

但这基本上是一团糟……你确定要这样做吗?看起来你正在将多个类型存储在一个数组中,尽管没有办法对它们进行多态使用,所以是否真的有充分的理由只使用一个数组而不是三个呢?


我想把所有的句柄放在一个空间数据结构中。这样我就可以查询哪些顶点、边和面在一个边界框内。数据结构的查询结果是“BaseHandles”。通过转换“BaseHandle”,我可以知道它是一个顶点、边还是面。一种解决方案是有三个空间数据结构,但那很丑陋。 - TerenceChill
1
说得好。不幸的是,如果没有多态性,你在这里所做的一切都会很丑陋,我认为三个独立的数组可能是最不丑陋的选择。 - Andy Newman

1

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

实时演示


1

如果所有派生自BaseHandle的类只使用单继承自BaseHandle(可能还继承自具有平凡析构函数的空类,这些类受到空基类优化的影响),并且除了非virtual函数和static成员之外不添加任何内容,而所有派生类都使用默认析构函数或等价物,则可以直接进行static_cast转换。

但要注意,无法知道其中的任何派生类是哪个。


如果我不知道BaseHandle指向的是哪个派生类,那么我如何使用static_cast来进行类型转换?没有虚函数意味着没有运行时类型信息(RTTI),所以我不能使用typeid来确定类型再进行转换,对吗? - TerenceChill
1
对的。你只需要知道,从BaseHandle的成员、外部知识或其他地方获取。如果有关系的话,也可能没有。 - Deduplicator

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接