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

发布于 2024-12-12 14:48:20 字数 1367 浏览 1 评论 0原文

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

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 类保留为非模板类​​型。

Some context: (Feel free to skip ahead) I have a module that processes complex data, but only has to know some semantics of it. The data can be considered a packet: The module should only reason about the opaque payload-string, but it will eventually pass the whole thing to a guy who needs more information. However, it has to ... "bundle" packets regarding some unknown packet information, so I came up with this:

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&);
};

Now, the idea is, that the user implements his PacketInfo semantics and passes that through my class. For instance:
(please read carefully the end of the question before answering)

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

At the point where I use a static_cast most people would use dynamic_cast but rtti is deactivated as a project-policy. Of course I can home brew my own type information, and I have done this before, but that is not the question here.

The question is: How can I get what I want (i.e. having a map key without knowing its content) without sacrificing type safety, that is, without casting at all? I would very much like to keep the Processor class a non-template type.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

爱殇璃 2024-12-19 14:48:25

我看到的最明显的问题是您的 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,或双重调度,
为了保证两个对象具有相同的类型,当你
比较它们(以及当它们不存在时如何排序比较)。

The most obvious problem I see is that your operator< and operator==
functions aren't const. So you can't call them through a pointer to
const or a reference to const. They should be:

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

Also, logically, if you define these two, you should define the other
four. The way I'll usually handle this is by defining a polymorphic
member function compare, which returns a value <, ==, or > 0,
depending on whether its this object is less than, equal to or greater
than the other object. This way, the derived classes only have one
function to implement.

Also, you definitely need some type of RTTI, or double dispatch, in
order to guarantee that both objects have the same type when you're
comparing them (and how you order the comparison when they don't).

何以笙箫默 2024-12-19 14:48:24

完全一般性的答案应该涉及双重调度。这个想法是,如果您有 n 个不同的 PacketInfo 子类,则需要比较运算符的 n * (n - 1) / 2 实现。事实上,如果将 CustomInfoAwesomePersonalInfo 进行比较,会发生什么?这涉及提前了解整个层次结构,这个问题中提供了示例代码。

如果您确定可以强制执行内部具有相同类型的映射(因此您确定只需要n个运算符实现),那么拥有 <代码>map。只需使用 map 即可。

有几种方法可以做到这一点。这里要做的最简单的事情是在数据包类型上对 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 对象并仅添加正确的数据包类型。在我看来,Processor 类必须是一个模板类。

此方法的名称为精简模板惯用语,其中底层实现不是类型安全的(以避免相对于模板的代码膨胀),但您添加了一层薄薄的模板来恢复接口级别的类型安全。

The answer in full generality should involve double dispatch. The idea is that if you have n different subclasses of PacketInfo, you need n * (n - 1) / 2 implementations of the comparison operator. Indeed, what happens if you compare a CustomInfo with a AwesomePersonalInfo ? This involves knowing the entire hierarchy ahead of time and sample code is presented in this SO question.

If you are certain you can enforce a map with identical types inside (and therefore you are certain you only need n operator implementations), then there is no point in having a map<PacketInfo, X>. Just use map<ConcretePacketInfo, X>.

There are several ways to do this. The simplest thing to do here is to template Processor on the packet type, possibly making it inherit from a BasicProcessor class if you want to "erase" the template parameter somewhere and factor common code.

Another cheap solution is the following: keep the code as is, but make Processor a template which only defines the relevant 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;
     }
};

This ensures that the caller will manipulate a Processor<CustomPacket> object and only add the correct packet type. The Processor class has to be a template class in my opinion.

This method goes by the name of Thin Template Idiom, where the underlying implementation is not type safe (to avoid code bloat relative to templates) but you add a thin layer of templates to restore type safety at the interface level.

悲念泪 2024-12-19 14:48:23

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

You can't. You either know the types at compile time, or check them at run time. There's no silver bullet.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文