多态(纯抽象)映射键牺牲了类型安全

Polymorphic (pure abstract) map key sacrifices type safety?

本文关键字:牺牲 类型安全 映射 抽象 多态      更新时间:2023-10-16

一些上下文:(可以随意跳过)我有一个处理复杂数据的模块,但只需要知道它的一些语义。数据可以被认为是一个数据包:模块应该只对不透明的有效负载字符串进行推理,但它最终会将整个数据传递给需要更多信息的人。然而,它不得不……关于一些未知包信息的"捆绑"包,所以我想出了这个:

struct PacketInfo {
  virtual void operator==(PacketInfo const&) const = 0;
  virtual void operator<(PacketInfo const&) const = 0;
  virtual ~PacketInfo() {}
};
class Processor {
  private:
    template <typename T> struct pless {
      bool operator()(T const* a, T const* b) const {
        assert(a && b);
        return *a < *b;
      }
    };
    // this is where the party takes place:
    std::map<PacketInfo const*,X,pless<PacketInfo> > packets;
  public:
    void addPacket(PacketInfo const*,X const&);
};

现在的想法是,用户实现了他的PacketInfo语义并将其传递给我的类。例如:
(请仔细阅读问题的结尾再回答)

struct CustomInfo : public PacketInfo {
  uint32_t source;
  uint32_t dest;
  void operator==(PacketInfo const& b) const {
    return static_cast<CustomInfo const&>(b).dest == dest
    && static_cast<CustomInfo const&>(b).source == source;
  }
  // operator< analogous
};

在我使用static_cast时,大多数人会使用dynamic_cast,但rtti作为项目策略被停用。当然,我可以自制我自己的类型信息,我以前也这样做过,但这不是这里的问题。

问题是:我如何才能得到我想要的(即有一个映射键,而不知道它的内容),而不牺牲类型安全,也就是说,根本不强制转换?我非常希望将Processor类保持为非模板类型。

你不能。您要么在编译时知道类型,要么在运行时检查它们。没有什么灵丹妙药。

一般来说,答案应该包括双重分派。其思想是,如果您有n不同的PacketInfo子类,则需要n * (n - 1) / 2实现比较操作符。实际上,如果比较CustomInfoAwesomePersonalInfo会发生什么?这需要提前了解整个层次结构,并在此SO问题中提供示例代码。

如果您确定可以在内部强制使用相同的类型的映射(因此您确定只需要n操作符实现),那么使用map<PacketInfo, X>就没有意义了。请使用map<ConcretePacketInfo, X>

有几种方法可以做到这一点。这里要做的最简单的事情是在数据包类型上模板Processor,如果您想要"擦除"模板参数并考虑公共代码,则可能使其从BasicProcessor类继承。

另一个便宜的解决方案如下:保持代码不变,但使Processor成为只定义相关addPacket的模板:

class BasicProcessor
{
private:
    template <typename T> struct pless 
    {
        bool operator()(T const* a, T const* b) const 
        {
            assert(a && b);
            return *a < *b;
        }
    };
protected:
    std::map<PacketInfo const*, X, pless<PacketInfo>> packets;
};
// You only need these lines in a public header file.
template <typename Packet>
class Processor : public BasicProcessor
{
public:
     void addPacket(Packet const* p, X const& x)
     {
         this->packets[p] = x;
     }
};

这确保调用者将操作Processor<CustomPacket>对象并只添加正确的数据包类型。在我看来,Processor类必须是模板类。

这个方法的名字是Thin Template Idiom,它的底层实现不是类型安全的(为了避免相对于模板的代码膨胀),但是你添加了一个薄的模板层来恢复接口级别的类型安全。

我看到的最明显的问题是你的operator<operator==函数不是const。所以你不能通过指针来调用它们Const或Const的引用。它们应该是:

virtual voie operator==(PacketInfo const& other) const = 0;
virtual voie operator<(PacketInfo const& other) const = 0;

同样,从逻辑上讲,如果定义了这两个,也应该定义另一个四。我通常会通过定义多态来处理这个问题成员函数compare,返回值<==> 0;取决于它的this对象是小于、等于还是大于而不是其他物体。这样,派生类就只有一个要实现的函数。

此外,您肯定需要某种类型的RTTI,或者双调度为了保证两个对象具有相同的类型比较它们(以及当它们不比较时如何排序)