【问题标题】:C++ Virtual Methods for Class-Specific Attributes or External Structure用于类特定属性或外部结构的 C++ 虚拟方法
【发布时间】:2010-06-08 17:37:36
【问题描述】:

我有一组类都派生自一个公共基类。我想多态地使用这些类。该接口定义了一组 getter 方法,它们的返回值在给定的派生类中是恒定的,但从一个派生类到另一个派生类不同。例如:

enum AVal
{
  A_VAL_ONE,
  A_VAL_TWO,
  A_VAL_THREE
};

enum BVal
{
  B_VAL_ONE,
  B_VAL_TWO,
  B_VAL_THREE
};

class Base
{
  //...
  virtual AVal getAVal() const = 0;
  virtual BVal getBVal() const = 0;
  //...
};

class One : public Base
{
  //...
  AVal getAVal() const { return A_VAL_ONE };
  BVal getBVal() const { return B_VAL_ONE };
  //...
};

class Two : public Base
{
  //...
  AVal getAVal() const { return A_VAL_TWO };
  BVal getBVal() const { return B_VAL_TWO };
  //...
};

等等

这是一种常见的做事方式吗?如果性能是一个重要的考虑因素,我最好将属性拉到外部结构中,例如:

struct Vals
{
  AVal a_val;
  VBal b_val;
};

在每个实例中存储一个Vals*,并重写Base如下?

class Base
{
  //...
  public:
    AVal getAVal() const { return _vals->a_val; };
    BVal getBVal() const { return _vals->b_val; };
  //...
  private:
    Vals* _vals;
};

额外的取消引用本质上与 vtable 查找相同吗?这种情况的成语是什么? 两个这些解决方案都愚蠢吗?非常感谢任何见解

【问题讨论】:

  • 设计清晰且易于扩展。除非这是您最内部的程序操作,否则这两种方法都不会出现在分析器的雷达上。

标签: c++ attributes virtual getter


【解决方案1】:

第一种方法看起来更清晰,并强制您覆盖这些方法(无论如何在第一个孩子身上)。我认为虚拟通话的开销往往低于人们的预期。只有当您分析代码并且虚拟调用花费大量时间时,我才会尝试按照您的第二种方法进行优化。

话虽如此,您要解决什么问题?有时像这样的类 id 很有用,但有时不同的接口抽象可以完成同样的事情,而根本不需要这样的接口。

【讨论】:

  • 嗯,我正在为我正在组合的脚本语言编写一个虚拟机。 免责声明: 这个项目纯粹是为了娱乐/探索/教育目的。 实际的类层次结构是一组脚本类型的原语/容器类。被访问的属性由各种“类型”信息组成(因此是类的常量,类集的变量)。因为打乱这些对象并执行这些 getter 是 vm 核心工作的重要组成部分,所以我想避免做一些虚伪或明显低效的事情。
【解决方案2】:

我个人会实现一种 GetTypeStats(),它返回一个(引用)结构,其中包含所有派生的特定信息,或者像您在 D3D 中找到的 QueryInterface。在这种情况下,您还应该考虑静态多态性。但是,如果您的类必须是运行时多态的,那么您实际上无法消除虚函数调用。

【讨论】:

  • 哦,是的,感谢您提出这个问题!目前的实现实际上确实使用了上面提到的外部结构体方法,并提供了一个getter方法来直接获取对结构体的const访问。
  • 你可以在基类中创建结构体,让派生类在构造函数中设置数据成员。这将删除 vfcalls。
  • 如果我没记错的话,这种方法(在基类中创建结构)的问题是每个实例都将包含结构的副本。使用虚方法或指向外部结构的指针,许多实例可以共享一组类型信息。我误会你了吗?
  • 管理这些指针的成本将远远超过复制结构的成本,尤其是当它只包含几个枚举值时。与恒定偏移访问相比,取消引用它,加上线程安全的智能指针开销,对于如此小的数据来说并不是一个好主意。
  • 当前的实现(请不要打我)不使用指向动态内存的智能指针。相反,每个结构的一个extern const 实例在每个派生类的头文件中声明并在相应的源文件中定义。这些结构的地址在派生构造函数中传递给基类,并在基类中分配给const struct*。这很丑吗?
【解决方案3】:

如果不同的是值并且它们在编译时是固定的,你可以将它们设为模板参数:

template< AVal aval, BVal bval>
class Derived : public Base
{
  AVal getAVal() const { return aval };
  BVal getBVal() const { return bval };
};

typedef Derived<A_VAL_ONE, B_VAL_ONE> One;
typedef Derived<A_VAL_TWO, B_VAL_TWO> Two;

【讨论】:

  • 然而,这并没有规避对虚拟的需求,并且比第一个示例中的显式虚拟覆盖更不可读(恕我直言)。
  • @acanaday:我猜 sbi 的帖子和我的一样,只是想给你一些额外的见解。不要讨厌,我们只是想帮忙! :)
  • 哦,对不起!无论哪种方式,我都非常感谢您的意见!
【解决方案4】:

当程序员想要避免使用dynamic_cast 时,这是为多态类型做穷人的dynamic_cast 的常用方法。在这些情况下,这是一个微优化。与所有微优化一样,您需要根据需要做出合理判断,然后再继续进行。如果分析器告诉您这样做没有性能提升,而不是 dynamic_cast,那么您最好使用 dynamic_cast

【讨论】:

  • 我并不反对你,但我不确定dynamic_cast 是我在此介绍的替代。例如,我可能会通过一个函数运行这些对象的列表,该函数的行为取决于从这些函数之一返回的值。您是否建议我尝试使用dynamic_cast 按顺序转换为派生类型,直到遇到不返回NULL 而不是打开getter 值的转换? (我是否严重误解了您的建议?)
  • 我还应该提到,确定从基础到派生的适当转换不一定是这里的目标。事实上,有时不需要铸造。我还是误会了吗?
  • @acanaday:我不是说它是。我要说的是,很多时候我看到这样的构造,这是对穷人dynamic_cast 的尝试。我的帖子可能适用于您,也可能不适用于您。
  • 哦,好的!感谢您的见解!我只是想确保我没有遗漏任何东西。
猜你喜欢
  • 1970-01-01
  • 2017-02-04
  • 1970-01-01
  • 1970-01-01
  • 2010-12-21
  • 1970-01-01
  • 2014-06-18
  • 2012-06-12
  • 1970-01-01
相关资源
最近更新 更多