【问题标题】:How to provide type safety in a two dimensional type hierarchy?如何在二维类型层次结构中提供类型安全?
【发布时间】:2017-02-10 20:28:15
【问题描述】:

考虑以下类层次结构:

class A {};
class B : public A {};
class C : public A {};
class D : public A {};

假设它不是平凡可约的:B、C 和 D 至少有一个成对的分离成员定义和至少一个从 A 继承的共享成员定义。

现在考虑一个包含指向第一个对象的指针的容器层次结构:

class AV : public std::vector<A*> {};
class BV : public AV {}; // contains only B*
class CV : public AV {}; // contains only C*
class DV : public AV {}; // contains only D*

再一次,假设它不是上面定义的简单可约化的。

这些类应该是“具有特征的容器”:为它们的元素和函数提供通用的容器函数,如“获取平均值”、“任何元素是否满足”等。 V 上的操作总是假定 { A, B, C ,D } 中 x 的类元素。

现在的问题是 AV 的子级提供了在 A* 上实例化的 std::vector 函数,而不是它们的实际内容分别为 B*、C*、D*。

我可以看到三种解决方案,都有不幸的缺点:

  1. 覆盖类 [B|C|D]V 中的 std::vector 成员以强制转换为正确的指针类型
    • 明显的缺点:大量样板代码
  2. 让 AV 不从 std::vector 继承,而是让 [B|C|D]V 从相应的 std::vector 实例化继承
    • 明显的缺点:AV 不能再用于只使用类 A 的元素成员的代码。
  3. 不要将 [A|B|C|D]V 定义为类层次结构,而是作为类模板,为每个模板参数 [A|B|C|D] 提供实现
    • 明显的缺点:隐式层次信息丢失,尤其是。使用 Visual Studio 等 IDE 工具 -> 代码变得更难导航

请注意,如果容器是类 [A|B|C|D]V 的成员而不是父类,问题仍然存在。

有没有更好的解决方案?如果是惯用的,怎么称呼?

为了清楚起见进行了简化:

  • C 指针而不是更合适的 std::weak_pointer
  • 简短的类名而不是告诉类名
  • 从 STL 容器继承可免费提供容器功能[s|ality],这也是继承的目的。

将 STL 容器作为成员提供会导致其大部分功能的委托实现如下:

class AV
{
public:
  auto begin() { return this->container.begin(); }
  auto end()   { return this->container.end(); }
  /* etc. pp. */
protected:
  std::vector<A*> container;
}

【问题讨论】:

  • 4.实现一个完全类型安全的模板。这将导致代码膨胀,但这就是 C++ 的全部意义所在。
  • 不要从 std::vector 继承。避免存储指针(甚至是智能指针),除非你需要多态性。
  • 我认为您应该重新表述避免从std::vector 继承的问题。这对读者来说是一个红鲱鱼。正如您所指出的,这对您的问题并不重要
  • 您隐含地和明确地提出了多个复杂的问题。最麻烦的是这种并行层次结构很容易破坏类型安全。考虑struct A{}; struct B:A{}; struct PA{ A*&amp; foo(); }; struct PB: PA { B*&amp; foo(); };,其中fooPA 中的PB 访问相同的原始指针成员。然后可以将PB实例绑定到PA&amp;,并且可以设置指针指向C,打破PB的类不变量。
  • 另一个复杂的问题与施工安全有关,避免两阶段施工(或更糟)。这是 C++ GUI 框架中解决的一个众所周知的问题。这样的框架通常为每个较低级别的 C API 小部件提供一个高级 C++ 小部件,即与您的问题中的并行层次结构大致相同。您添加的主要复杂性是每个高级对象的一个​​ wrappee 向量,而不是单个 wrappee。看起来还是一样的问题。

标签: c++ inheritance type-conversion c++17


【解决方案1】:

应该可以的。

template<typename L>
class  V : public std::vector<L*> {}; // all members of original AV
class AV : public V<A> {}; // now empty
class BV : public V<B> {}; // as before
class CV : public V<C> {}; // "    "
class DV : public V<D> {}; // "    "

如果我没记错的话,与原始设置相比,应该没有进一步的限制。

在这一点上,我们距离@SamVarshavchik 的原始评论仅一步之遥,顺便说一句,这是关于这个问题的第一个活动。有时最简单的答案是最好的——也是最难发现的。

【讨论】:

  • 注意:我知道我可以通过用另一个模板类 V 替换 AV 并将 AV 提升为与 [B|C|D]V 相同层上的非模板来改进这一点。编辑问题以反映这一点意味着摆脱对默认模板参数的混淆,我想看看是否有人可以启发我;在一段时间内,我将提供相应的编辑,使这个答案可以接受。
  • 我不确定这是否是问题的答案。我觉得这些信息更适合作为第四个解决方案,但有一个不幸的缺点。
  • @Zsar: "根据 cppreference.com 这不应该是这种情况" AVAV&lt;&gt; 之间是有区别的。前者是模板;后者是一个类。
  • @Tas :我认为它通过了,因为必要的修改可以通过搜索和替换来完成,没有困难或容易出错的正则表达式。可以说,考虑到他们确实解决了手头的问题,1-3 本身可以是答案,但是他们任意定义的适合度太低而无法考虑。我实际上可能会选择这个(或者更确切地说:使用基模板类 V,[A|B|C|D]V 从它继承,如我的评论中所述)。
【解决方案2】:

BV 不是AV。那里不应该有继承,因为BV 没有有用的方法来满足合理有用的可变AV 的不变量。在这里使用 (C++) 继承是有害的。

这是一个古老的矩形-正方形问题,其中不可变正方形是不可变矩形,但可变正方形不是可变矩形。

有 3 个插脚。阅读、写作和实施。

AV_readerBV_reader 的基类(等等)。 BV 上的非变异操作满足AV 的reasonabke 矿石和后置条件,假设AB 也是如此。

对于写作,情况并非如此; BV_writer 不是 AV_writer 的一种。可能它可以颠倒过来:AV_writer 是一种BV_writer,但这又依赖于AB 属性,这些属性比读取不变量少一些。还有一些扩展 (BV_writer_augment) 不会成为AV_writer 的一部分。参数的逆变可能让你免费获得这个。

在实现方面,使用vector&lt;A*&gt; 来存储vector&lt;B*&gt; 并将其包装在无数个内外转换中很容易出错并且很痛苦。

结论是:

您需要协变读取、逆变写入(我认为这两个名称是正确的:我有时会颠倒它们)和一个类似 CRTP 的模板实现系统,该系统执行类型检查,存储实际的底层向量,而不强制不强制转换。

这与 C++ 默认的多态继承指针系统不匹配。所以不要使用C++指针多态继承。

现在的问题是正确的解决方案很难,并且需要一些样板。但这是正确的做法。

【讨论】:

  • 嗯。也许我没有指定。 Mayhap 名字有帮助。假设 AV 是“UnitList”,BV 是“LandUnitList”; AV 的所有功能都必须在 BV 中可用,有些是虚拟的和被覆盖的,但大多数只是继承。在那种情况下,A 将是“Unit”,B 将是“LandUnit”。 LandUnit 会有额外的值,但所有的单位值,单位的所有功能。 Unit->LandUnit 和 UnitList->LandUnitList 都是有效的继承,只要后者总是(并用作)功能齐全的前者。
  • @zsar 违反LSP,如果您可以写入元素或将元素添加到AV。将LandUnit 添加到实际上是WaterUnitListUnitList 是这种违规症状的一个示例。 LandUnit 可以是 Unit,但 LandUnitList 如果有类写操作就不是 UnitList,因为 LandUnitList 的写前置条件(源是 LandUnit)比 UnitList 写前置条件(源是单元)。 ReadOnlyLandUnitList 是 ReadOnlyUnitList。
  • 有趣的是,WriteOnlyUnitList 是 WriteOnlyLandUnitList,因为 WriteOnlyUnitList 的先决条件比 WriteOnlyLandUnitList 。如果你不熟悉这个技巧,你会想坐下来重新考虑你的直觉“到底是什么,不”。 (简而言之,写“继承”是从阅读倒退)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-09-14
  • 2013-03-20
  • 1970-01-01
  • 2013-12-25
  • 1970-01-01
相关资源
最近更新 更多