多态(纯抽象)映射键牺牲类型安全?
一些上下文:(可以随意跳过)我有一个处理复杂数据的模块,但只需要知道它的一些语义。数据可以被认为是一个数据包:模块应该只推理不透明的有效负载字符串,但它最终会将整个事情传递给需要更多信息的人。然而,它必须......“捆绑”有关一些未知数据包信息的数据包,所以我想出了这个:
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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
我看到的最明显的问题是您的
operator<
和operator==
函数不是 const。所以你不能通过指针来调用它们
const 或对 const 的引用。它们应该是:
另外,从逻辑上讲,如果定义了这两个,则应该定义另一个
四。我通常处理这个问题的方法是定义一个多态
成员函数
compare
,返回值<
、==
或>
0,取决于它的
this
对象是否小于、等于或大于比其他物体。这样,派生类就只有一个
来实现的函数。
另外,您肯定需要某种类型的 RTTI,或双重调度,
为了保证两个对象具有相同的类型,当你
比较它们(以及当它们不存在时如何排序比较)。
The most obvious problem I see is that your
operator<
andoperator==
functions aren't const. So you can't call them through a pointer to
const or a reference to const. They should be:
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 greaterthan 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).
完全一般性的答案应该涉及双重调度。这个想法是,如果您有
n
个不同的PacketInfo
子类,则需要比较运算符的n * (n - 1) / 2
实现。事实上,如果将CustomInfo
与AwesomePersonalInfo
进行比较,会发生什么?这涉及提前了解整个层次结构,这个问题中提供了示例代码。如果您确定可以强制执行内部具有相同类型的映射(因此您确定只需要。只需使用
n
个运算符实现),那么拥有 <代码>mapmap
即可。有几种方法可以做到这一点。这里要做的最简单的事情是在数据包类型上对 Processor 进行模板化,如果您想在某处“擦除”模板参数并考虑因素,则可能使其继承自 BasicProcessor 类通用代码。
另一个廉价的解决方案如下:保持代码不变,但使
Processor
成为一个仅定义相关addPacket
的模板:这确保调用者将操作
Processor
对象并仅添加正确的数据包类型。在我看来,Processor
类必须是一个模板类。此方法的名称为精简模板惯用语,其中底层实现不是类型安全的(以避免相对于模板的代码膨胀),但您添加了一层薄薄的模板来恢复接口级别的类型安全。
The answer in full generality should involve double dispatch. The idea is that if you have
n
different subclasses ofPacketInfo
, you needn * (n - 1) / 2
implementations of the comparison operator. Indeed, what happens if you compare aCustomInfo
with aAwesomePersonalInfo
? 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 amap<PacketInfo, X>
. Just usemap<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 aBasicProcessor
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 relevantaddPacket
:This ensures that the caller will manipulate a
Processor<CustomPacket>
object and only add the correct packet type. TheProcessor
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.
你不能。您要么在编译时知道类型,要么在运行时检查它们。没有灵丹妙药。
You can't. You either know the types at compile time, or check them at run time. There's no silver bullet.