多态(纯抽象)映射键牺牲了类型安全
Polymorphic (pure abstract) map key sacrifices type safety?
一些上下文:(可以随意跳过)我有一个处理复杂数据的模块,但只需要知道它的一些语义。数据可以被认为是一个数据包:模块应该只对不透明的有效负载字符串进行推理,但它最终会将整个数据传递给需要更多信息的人。然而,它不得不……关于一些未知包信息的"捆绑"包,所以我想出了这个:
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
实现比较操作符。实际上,如果比较CustomInfo
和AwesomePersonalInfo
会发生什么?这需要提前了解整个层次结构,并在此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,或者双调度为了保证两个对象具有相同的类型比较它们(以及当它们不比较时如何排序)
- 为表示一个或多个操作的C++函数的int参数寻找类型安全的替换
- 附加类型安全的子类std::string
- 什么是 c/c++ 中的类型安全
- 在模板类中使用'new'类型安全吗?
- 类型安全可变参数函数
- 类型安全 - all_of/ any_of/ none_of for std::tuple
- 如何在具有模板函数的类中使用类型安全的联合(变体)
- 将自动类型变量初始化为零.这种类型安全吗?
- 在 C++ 中强制实施类型安全,而无需使用额外的类
- 用于memcpy的类型安全C 包装器
- 以更健壮和类型安全的方式处理ASCII命令
- 是否有任何类型安全的、编译时检查的 printf 的实现
- 如何在C++中使类型安全字符串"definition"对象?
- 以最新的C 的类型安全方式从枚举中随机选择元素
- 新的类型安全枚举是否定义为从 0 开始
- C++(类似SBT)的类型安全构建系统
- 初始值设定项列表引用类型安全
- 类型安全的c++11枚举类标志的模板
- valarray<valarray<内置类型> >安全吗?
- 多态(纯抽象)映射键牺牲了类型安全