首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
社区首页 >问答首页 >到其他已知的超类

到其他已知的超类
EN

Stack Overflow用户
提问于 2021-03-28 18:07:17
回答 2查看 120关注 0票数 0

我有一个从另外两个(虚拟)类继承的类。让我们调用我的两个超类A和B。我可以有只从A继承的对象,只从B继承的对象,以及从A和B继承的对象。现在,我希望在某个容器中存放已知从A和B继承的对象:为C/Java语法mashup道歉,但这可能是这样的:std::vector<? : A, B>。现在,这是不存在的,但是事实证明,保存我的容器的类主要使用这些类的"A“功能,这就是我目前使用std::vector<A*>的原因。

然而,在我的代码中的另一个地方,在另一个地方,在不同的地方,访问相同的向量,但目的是使用"B“功能。

虽然我个人知道我已经确保了添加到我的向量中的所有实例都是从A和B扩展的,但是编译器没有。这显然使我很难访问B功能。

因此,我的问题是:我如何从A到B“旁白”?我有一个指向A的指针,它的动态类型类似于C (即,继承自A和B-但不一定是特定的C),但我希望获得指向B的指针,显然不需要更改动态类型。

dynamic_cast做了这项工作,但我认为这是一种丑陋的做法。有更好的主意吗?

EN

回答 2

Stack Overflow用户

发布于 2021-03-28 23:00:49

进行侧播的方法是使用dynamic_cast。(你甚至可以对此进行测试。前往cppreference.com搜索“侧播”。只有一次命中。dynamic_cast将使用它的动态魔力来确保指向对象实际上继承了新类(例如,B),并动态地确定需要向指针中添加什么偏移量来执行侧广播。

我不认为dynamic_cast方法“丑陋”,但我确实发现它缺乏优雅。可以接受,但不是很想要。我更倾向于一种更结构化的方法,因为有人断言所讨论的对象都是从AB继承的。(如果不是因为这个断言,dynamic_cast可能是正确的方法。)

在某个容器中,有一个容器保存已知从AB继承的对象。这样的物品有名字吗?如果是这样的话,这可能是一个新类的基础,称为AB,它继承了AB。它可能只会继承这两个类,但它仍然可以在容器中发挥有用的作用。如果容器要保存指向AB的指针,那么容器将自动记录所有元素必须同时从AB派生的文档。此外,不需要动态检查类型,也不需要处理侧广播失败。编译器将强制要求对象必须同时从AB派生,才能首先添加到容器中。此外,不需要动态计算偏移量,因为该偏移量被放入AB中。总体结果是代码更干净,在编译时进行更多的检查。我更喜欢这个解决方案。

OP的一条评论认为,这个提议的AB“基本上是空的,只能充当包装器,这有点难看”。我不同意“丑陋”这一说法,尽管我并不是说这种观点是错误的。不过,我要指出,这是有先例的。就公共接口而言,std::iostream基本上是完全空的,只用作std::istreamstd::ostream的包装器。它的作用类似于我对AB的建议。欢迎您认为std::iostream是丑陋的,但这是无可否认的标准。

票数 0
EN

Stack Overflow用户

发布于 2021-03-28 23:37:40

不能保证AB在对象中的地址是相同的,甚至对于所有的CDEF,这些地址之间的差异也是一样的。这意味着您需要为向量的每个元素提供额外的数据,这将告诉您如何获得指向基类A和B的指针。

dynamic_cast从虚拟表中提取这些附加数据(假设编译器通过虚拟表实现RTTI )。

优点:

  • 没有内存开销。
  • 可伸缩的,如果您想要添加第三个、第四个等等基类。

缺点:

  • 需要RTTI。
  • dynamic_cast可能有些慢(如果它是通过遍历类层次结构实现的)。

可能的替代办法:

1.适配器

代码语言:javascript
代码运行次数:0
运行
复制
class Adapter {
 public:
  template<class C>
  /* implicit */ Adapter(C* pointer):
    pointer_a_(pointer),
    pointer_b_(pointer) {
        static_assert(std::is_base_of_v<A, C> && std::is_base_of_v<B, C>,
                      "C should be derived from both A and B.");
  }

  A* getA() const { return pointer_a_; }
  B* getB() const { return pointer_b_; }

 private:
  A* pointer_a_ = nullptr;
  B* pointer_b_ = nullptr;
};

int main() {
  std::vector<Adapter> my_vector;
  C1 c1;
  C2 c2;
  my_vector.emplace_back(&c1);
  my_vector.emplace_back(&c2);
  for (Adapter& elem : my_vector) {
    elem->getA()->foo();
    elem->getB()->bar();
  }
}

优点:

  • 不需要RTTI。
  • 快地。
  • 可以确保在编译时向量中的对象实际上是从AB派生的。

缺点:

  • 来自附加指针的内存开销(每个对象)。

2.添加虚拟函数B* A::getB()。

代码语言:javascript
代码运行次数:0
运行
复制
class A {
 public:
  // Return this (casted to B*), or nullptr if the dynamic object is not derived from B.
  virtual B* getB() { return nullptr; }
  // ...
};

class C : public FooWhichInheritsB, public A {
 public:
  B* getB() override { return this; }
};

int main() {
  std::vector<A*> my_vector;
  C1 c1;
  C2 c2;
  my_vector.emplace_back(&c1);
  my_vector.emplace_back(&c2);
  for (A* elem : my_vector) {
    elem->foo();
    elem->getB()->bar();
  }
}

优点:

  • 不需要RTTI。
  • 没有内存开销(每个对象)。
  • 调用虚拟函数getB()比执行dynamic_cast更快。

缺点:

  • 调用虚拟函数getB()比简单地读取Adapter实现中的pointer_b_成员要慢。
  • 您需要记住在每个CDEF中重写D,.

3.引入一个公共基类AB

代码语言:javascript
代码运行次数:0
运行
复制
class AB : public A, public B {};
class C : public AB { /* ... */ };
class D : public AB { /* ... */ };

优点:

  • 不需要RTTI。
  • 没有内存开销(每个对象)。
  • 甚至比适配器还快。

缺点:

  • 在类层次结构上添加约束--您不能再向向量中添加class Foo : public A2, public B {};类型的对象(其中A2是从A派生的)。
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/66844580

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档