在看到this question之后,我首先想到的是定义泛型等价和关系运算符是微不足道的:
#include <cstring>
template<class T>
bool operator==(const T& a, const T& b) {
return std::memcmp(&a, &b, sizeof(T)) == 0;
}
template<class T>
bool operator<(const T& a, const T& b) {
return std::memcmp(&a, &b, sizeof(T)) < 0;
}
然后 using namespace std::rel_ops
将变得更加有用,因为它将通过运算符==
和<
的默认实现完全通用。显然,这不执行成员比较,而是执行逐位比较,就好像类型仅包含POD成员一样。这与C ++生成复制构造函数的方式不完全一致,例如,做执行成员复制。
但我想知道上述实施是否确实安全。结构自然会具有相同的包装,具有相同的类型,但是填充的内容保证是相同的(例如,用零填充)?是否有任何理由或情况不起作用?
答案 0 :(得分:13)
不 - 只是例如,如果你有T ==(float | double | long double),你的operator==
就无法正常工作。两个NaN不应该相等,即使它们具有相同的位模式(实际上,检测NaN的一种常用方法是将数字与自身进行比较 - 如果它不等于它自身,则它是NaN)。同样,两个浮点数的指数中的所有位都设置为0,其值为0.0(确切),无论有效位中可能设置/清除哪些位。
您operator<
的工作机会更少。例如,考虑std::string
的典型实现,如下所示:
template <class charT>
class string {
charT *data;
size_t length;
size_t buffer_size;
public:
// ...
};
通过成员的这种排序,您的operator<
将根据字符串碰巧存储其数据的缓冲区的地址进行比较。例如,如果它恰好是先用length
成员编写的,那么您的比较将使用字符串的长度作为主键。在任何情况下,都不会根据实际的字符串内容进行比较,因为它只会查看data
指针的值,而不是它指向的指针,是你真正想要/需要的。
编辑:就填充而言,并不要求填充的内容相等。理论上,填充也可能是某种陷阱表示,如果您甚至试图查看它,它将导致信号,抛出异常或该命令中的某些内容。为了避免这种陷阱表示,您需要使用类似转换的东西来将其视为unsigned char
的缓冲区。 memcmp
可能会这样做,但话又说明......
另请注意,作为相同类型的对象,不必然意味着使用相同的成员对齐方式。这是一种常见的实现方法,但编译器完全有可能做一些事情,比如根据“认为”特定对象的使用频率使用不同的对齐方式,并在>中包含某种类型的标记对象(例如,写入第一个填充字节的值),它告诉该特定实例的对齐。同样,它可以通过(例如)地址隔离对象,因此位于偶数地址的对象具有2字节对齐,在4的倍数的地址处具有4字节对齐,依此类推(这不可能是用于POD类型,但除此之外,所有投注均已关闭。
这些都不可能或不常见,但是我不能想到禁止它们的标准中的任何内容。
答案 1 :(得分:5)
答案 2 :(得分:3)
即使对于POD,==运算符也可能是错误的。这是由于结构的对齐,如下所示,我的编译器需要8个字节。
class Foo {
char foo; /// three bytes between foo and bar
int bar;
};
答案 3 :(得分:2)
这是非常危险的,因为编译器不仅会对普通的旧结构使用这些定义,而且对于任何类,无论多么复杂,您都忘记正确定义==
和<
。
有一天,它会咬你。
答案 4 :(得分:0)
很多可能取决于你对等效的定义。
e.g。如果您在类中比较的任何成员是浮点数。
上述实现可能会将两个双精度视为不相等,即使它们来自相同输入的相同数学计算 - 因为它们可能没有生成完全相同的输出 - 而是两个非常相似的数字。
通常,这些数字应在数值上与适当的容差进行比较。
答案 5 :(得分:0)
任何包含单个指针的结构或类都会立即失败任何有意义的比较。这些运算符只适用于普通旧数据或POD的任何类。另一个回答者正确地指出了浮点作为一种情况,即使这样也不成立,并填充字节。
简短的回答:如果这是一个聪明的主意,那么语言会像默认的复制构造函数/赋值运算符一样。