【问题标题】:Is this a safe way to implement a generic operator== and operator<?这是实现通用 operator== 和 operator< 的安全方法吗?
【发布时间】:2011-04-06 07:42:19
【问题描述】:

看到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 将变得更加有用,因为它会被运算符 ==&lt; 的默认实现完全通用。显然,这不会执行成员比较,而是按位比较,就好像该类型仅包含 POD 成员一样。这与 C++ 生成复制构造函数的方式并不完全一致,例如,确实执行成员复制。

但我想知道上面的实现是否确实安全。这些结构自然会具有相同的包装,属于相同的类型,但填充的内容是否保证相同(例如,用零填充)?是否有任何原因或情况导致这不起作用?

【问题讨论】:

  • 您为什么要这样做?如果没有这个,如果你没有相等或小于运算符,你会得到一个非常方便的编译错误。有了这个,编译错误就消失了,你默默地拥有了可能是错误的运算符。您正在以不易被发现的方式小心地创建错误。
  • @David:我几乎可以肯定不会在实际代码中使用它,但推测一下也无妨。
  • 从好的方面来说,如果你刚刚被解雇,我认为这是对你的公司进行报复的好方法。比#define NULL rand()%1000 ? 0:1更好
  • @DumbCoder:这基本上就是我正在寻找的答案。发布它,我会接受。

标签: c++ operators operator-overloading


【解决方案1】:

除非你对内存布局、编译器行为有 100% 的把握,而且你真的不关心可移植性,并且你真的想提高效率,否则永远不要这样做

SOURCE

【讨论】:

    【解决方案2】:

    不——例如,如果您有 T==(float | double | long double),则您的 operator== 无法正常工作。两个 NaN 永远不应该比较相等,即使它们具有相同的位模式(实际上,检测 NaN 的一种常用方法是将数字与自身进行比较——如果它不等于自身,则它是 NaN)。同样,指数中的所有位都设置为 0 的两个浮点数的值(精确)为 0.0,无论有效位中可能设置/清除哪些位。

    您的operator&lt; 正常工作的机会更小。例如,考虑一个典型的 std::string 实现,如下所示:

    template <class charT>
    class string { 
        charT *data;
        size_t length;
        size_t buffer_size;
    public:
        // ...
    };
    

    通过这种成员排序,您的operator&lt; 将根据字符串恰好存储其数据的缓冲区的地址进行比较。例如,如果它碰巧是先用length 成员编写的,那么您的比较将使用字符串的长度作为主键。在任何情况下,它不会根据实际的字符串内容进行比较,因为它只会查看data 指针的值,而不是它指向的任何内容,这就是你真的想要/需要。

    编辑:就填充而言,不要求填充的内容相等。从理论上讲,填充也可能是某种陷阱表示,它会导致信号、抛出异常或按该顺序排列的东西,如果您甚至尝试查看它的话。为了避免这种陷阱表示,您需要使用类似强制转换的东西将其视为unsigned chars 的缓冲区。 memcmp 可能会这样做,但又可能不会......

    还要注意,相同类型的对象不一定意味着使用相同的成员对齐方式。这是一种常见的实现方法,但编译器也完全有可能根据它“认为”特定对象的使用频率使用不同的对齐方式,并在 in 中包含某种标签em> 告诉此特定实例的对齐方式的对象(例如,写入第一个填充字节的值)。同样,它可以按(例如)地址分隔对象,因此位于偶数地址的对象具有 2 字节对齐,位于四的倍数的地址具有 4 字节对齐,依此类推(这不能用于 POD 类型,否则,所有赌注都关闭)。

    这些都不太可能或常见,但我想不出标准中任何禁止它们的东西。

    【讨论】:

    • 我非常清楚它在大多数情况下可能无法正常工作。我要问的是它是否调用未定义、未指定或实现定义的行为来在(可能不正确)假设它们是 POD 的情况下对结构进行按位比较。
    • 您如何确定某个类型的各个实例可以具有独特的对齐要求?至少,这会影响对象的大小,这是非常不允许的。我可以看到它可能在哪里,它似乎与我对对齐规则的理解不符。
    • @Dennis:好吧,我很确定标准中没有任何内容专门允许它,但我也想不出任何会禁止它的东西。唯一看起来有用的时候是当它被用作基类时,它放松了关于 sizeof(base_subobject) 的规则。改变对齐方式也不一定改变大小——它只是改变你放置填充的位置(在结构的末尾与成员之间)——尽管它确实消除了这样做的大部分动机。
    • 是的,我想得越多,我就越不相信有什么禁止它的。实现起来会非常尴尬,我无法想象它实际上是有益的,除非作为一个基类。
    【解决方案3】:

    这是非常危险的,因为编译器不仅会将这些定义用于普通的旧结构,而且还会用于您忘记正确定义 ==&lt; 的任何类,无论多么复杂。

    总有一天,它会咬你。

    【讨论】:

    • 对于默认复制构造函数和赋值运算符,您可以说完全相同。从设计的角度来看这有多危险并不是我的问题。从未定义/未指定/实现定义的行为角度来看,这是多么危险。
    【解决方案4】:

    任何包含单个指针的结构或类都会立即使任何有意义的比较失败。这些运算符仅适用于普通旧数据或 POD 的任何类。另一位回答者正确地指出浮点数是一种情况,即使这样也不成立,并填充字节。

    简短的回答:如果这是一个聪明的想法,语言会像默认的复制构造函数/赋值运算符一样。

    【讨论】:

      【解决方案5】:

      即使对于 POD,== 运算符也可能是错误的。这是由于以下结构的对齐方式在我的编译器上占用 8 个字节。

      class Foo {
        char foo; /// three bytes between foo and bar
        int bar;
      };
      

      【讨论】:

        【解决方案6】:

        很大程度上取决于您对等价的定义。

        例如如果您在类中比较的任何成员是浮点数。

        上述实现可能将两个双精度数视为不相等,即使它们来自具有相同输入的相同数学计算 - 因为它们可能不会产生完全相同的输出 - 而是两个非常相似的数字。

        通常,此类数字应在数值上与适当的容差进行比较。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-06-07
          • 1970-01-01
          • 2013-12-02
          • 1970-01-01
          • 2010-12-21
          • 2016-04-02
          • 1970-01-01
          • 2013-04-12
          相关资源
          最近更新 更多