【问题标题】:How to compare objects of POD types如何比较 POD 类型的对象
【发布时间】:2011-09-25 18:47:35
【问题描述】:

这个例子:

#include <iostream>
#include <cstring>

struct A
{
    int  a;
    bool b;
};

bool foo( const A a1, const A a2 )
{
    return ( 0 == std::memcmp( &a1, &a2, sizeof( A ) ) );
}

int main()
{
    A a1 = A();
    a1.a = 5;a1.b = true;
    A a2 = A();
    a2.a = 5;a2.b = true;

    std::cout<<std::boolalpha << foo( a1, a2 ) << std::endl;
}

由于填充,将产生false

我无权访问foo 函数,也无法更改完成比较的方式。

假设 bool 占用 1 个字节(在我的系统上是这样),如果我将 struct A 更改为:

struct A
{
  int a;
  bool b;
  char dummy[3];
};

然后它在我的系统上运行良好(输出为true)。

我还能做些什么来解决上述问题(获取true 输出)?

【问题讨论】:

标签: c++


【解决方案1】:

由于结构中的填充,第一个不起作用。两个对象的填充具有不同的位模式。

如果你在使用之前使用memset设置对象中的所有位,那么它将起作用:

A a1;
std::memset(&a1, 0, sizeof(A));
a1.a = 5;a1.b = true;

A a2;
std::memset(&a2, 0, sizeof(A));
a2.a = 5;a2.b = true;

在线演示:


顺便说一句,您也可以为 POD 写 operator&lt;operator== 等。

【讨论】:

  • A a1 = A();A a1; std::memset(&amp;a1, 0, sizeof(A)); 相同。我可以为 POD 编写运算符,但我无权访问进行比较的函数,因此我坚持使用 std::memcmp
  • @VJo:不。它不一样 A a1=A() 仅初始化成员,而不是填充。也就是说,A a1=A() 等价于 A a1; memset(&amp;a1.a, 0, sizeof(a1.a)); memset(&amp;a1.b, 0, sizeof(a1.b));。剩余的(sizeof(A) - sizeof(a1.a) - sizeof(a1.b)) 字节保持原样,包含垃圾。
  • 好吧,严格来说,A a1=A() 不能保证初始化填充,但它仍然可以这样做。
【解决方案2】:

从 C++11 开始,我们可以使用元组进行简单的 POD 比较(元组对 &gt;&lt;&gt;=&lt;= 运算符使用字典比较,更多信息:https://en.cppreference.com/w/cpp/utility/tuple/operator_cmp):

#include <iostream>
#include <tuple>

struct Point {
    int x;
    int y;
    int z;    
};


auto pointToTuple(const Point& p) {
    return std::make_tuple(p.x, p.y, p.z);
}

bool operator==(const Point& lhs, const Point& rhs ) {
    return pointToTuple(lhs) == pointToTuple(rhs);
}

bool operator<(const Point& lhs, const Point& rhs ) {
    return pointToTuple(lhs) < pointToTuple(rhs);
}

int main()
{

    Point a{1, 2, 3};
    Point b{1, 2, 3};
    Point c{2, 2, 2};

    std::cout << (pointToTuple(a) == pointToTuple(b) ? "true" : "false") << "\n"; //true
    std::cout << (pointToTuple(a) == pointToTuple(c) ? "true" : "false") << "\n"; //false

    std::cout << (a == b ? "true" : "false") << "\n"; //true
    std::cout << (a == c ? "true" : "false") << "\n"; //false

    std::cout << (a < b ? "true" : "false") << "\n"; //false
    std::cout << (a < c ? "true" : "false") << "\n"; //true

}

C++20 应该为我们提供默认比较 (https://en.cppreference.com/w/cpp/language/default_comparisons)。因此,如果类将operator&lt;=&gt; 定义为默认值,编译器将自动生成==!=&lt;&lt;=&gt;&gt;= 运算符和它们的代码:

struct Point {
    int x;
    int y;
    int z;    

    auto operator<=>(const Point&) const = default;
};

【讨论】:

    【解决方案3】:

    在 C++14 及更高版本中,您可以使用此库:https://github.com/apolukhin/magic_get/ 来提取 POD 的成员类型。你可以编写通用比较运算符,它不需要原始对象的 memsetting 内存来擦除填充,如下所示:

    #include "boost/pfr/precise.hpp"
    template<typename T>
    void foo(const T& a, const T& b)
    {
        return boost::pfr::flat_less<T>{}(a, b);
    }
    

    此方法的优点是不修改创建对象的代码(当它不受您控制时可能很有价值),但它也会生成额外的二进制代码,并且使用 PFR 库进行编译会更慢。

    仍然 - 它是最灵活和最干净的,因为简单的 memcmp 不会为您提供真正的语义功能(例如,当您对 POD 的子类型使用自定义比较运算符时)。


    PS:使用 PFR 库,您可以对 POD 执行多种其他操作,例如打印它们、迭代成员等。在此处查看更多示例:

    http://apolukhin.github.io/magic_get/boost_precise_and_flat_reflectio/short_examples_for_the_impatient.html

    【讨论】:

    • 不知道这样的库存在)你提到它真的很好。我注意到这个库允许您比较和打印结构。您能否通过示例(例如apolukhin.github.io/magic_get/boost_precise_and_flat_reflectio/…)更新您的答案,以使您的答案非常棒?
    • @DenisSablukov:我将添加示例链接,因为这超出了这个问题的范围,但可以肯定; )。顺便说一句 - 我刚刚注意到它不需要 C++17,14 就足够了。
    猜你喜欢
    • 1970-01-01
    • 2010-10-17
    • 2010-10-16
    • 2021-12-31
    • 2023-03-11
    • 1970-01-01
    • 2012-05-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多