繁体   English   中英

如何比较 C++ 中的泛型结构?

[英]How to compare generic structs in C++?

我想以通用的方式比较结构,我已经做了这样的事情(我不能分享实际的来源,所以如果有必要,请询问更多细节):

template<typename Data>
bool structCmp(Data data1, Data data2)
{
  void* dataStart1 = (std::uint8_t*)&data1;
  void* dataStart2 = (std::uint8_t*)&data2;
  return memcmp(dataStart1, dataStart2, sizeof(Data)) == 0;
}

这主要按预期工作,但有时即使两个结构实例具有相同的成员(我已经使用 eclipse 调试器检查过),它也会返回 false。 经过一番搜索,我发现memcmp可能会由于填充使用的结构而失败。

有没有更合适的方法来比较对填充无动于衷的内存? 我无法修改使用的结构(它们是我正在使用的 API 的一部分),并且使用的许多不同结构具有一些不同的成员,因此无法以通用方式单独比较(据我所知)。

编辑:不幸的是,我被 C++11 困住了。 早该提过这个...

你是对的,填充会妨碍你以这种方式比较任意类型。

您可以采取以下措施:

  • 如果您控制Data则例如 gcc 具有__attribute__((packed)) 它对性能有影响,但可能值得一试。 不过,我不得不承认,我不知道packed能让您完全禁止填充。 Gcc 文档说:

此属性附加到结构或联合类型定义,指定结构或联合的每个成员被放置以最小化所需的内存。 当附加到枚举定义时,它表示应使用最小的整数类型。

如果 T 是 TriviallyCopyable 并且如果任何两个具有相同值的 T 类型对象具有相同的对象表示,则提供等于 true 的成员常量 value。 对于任何其他类型,值为 false。

并进一步:

引入此特征是为了通过将其对象表示散列为字节数组来确定是否可以正确散列类型成为可能。

PS:我只解决了填充问题,但不要忘记,对于在内存中具有不同表示形式的实例,可以比较相等的类型绝非罕见(例如std::stringstd::vector和许多其他)。

不, memcmp不适合这样做。 而此时 C++ 中的反射不足以做到这一点(将有实验性编译器支持足够强大的反射来做到这一点,而可能具有您需要的功能)。

如果没有内置反射,解决问题的最简单方法是进行一些手动反射。

拿着这个:

struct some_struct {
  int x;
  double d1, d2;
  char c;
};

我们想做最少的工作,这样我们就可以比较其中的两个。

如果我们有:

auto as_tie(some_struct const& s){ 
  return std::tie( s.x, s.d1, s.d2, s.c );
}

或者

auto as_tie(some_struct const& s)
-> decltype(std::tie( s.x, s.d1, s.d2, s.c ))
{
  return std::tie( s.x, s.d1, s.d2, s.c );
}

对于 ,则:

template<class S>
bool are_equal( S const& lhs, S const& rhs ) {
  return as_tie(lhs) == as_tie(rhs);
}

做得相当不错。

我们可以通过一些工作将这个过程扩展为递归; 不是比较关系,而是比较包装在模板中的每个元素,并且该模板的operator==递归地应用此规则(将元素包装在as_tie进行比较),除非该元素已经具有有效的==并处理数组。

这将需要一些库(100 行代码?)以及编写一些手动的每个成员“反射”数据。 如果您拥有的结构数量有限,手动编写每个结构的代码可能会更容易。


大概有办法获得

REFLECT( some_struct, x, d1, d2, c )

使用可怕的宏生成as_tie结构。 但是as_tie已经足够简单了。 ,重复很烦人; 这很有用:

#define RETURNS(...) \
  noexcept(noexcept(__VA_ARGS__)) \
  -> decltype(__VA_ARGS__) \
  { return __VA_ARGS__; }

在这种情况和许多其他情况下。 使用RETURNS ,写as_tie是:

auto as_tie(some_struct const& s)
  RETURNS( std::tie( s.x, s.d1, s.d2, s.c ) )

去除重复。


这是使其递归的尝试:

template<class T,
  typename std::enable_if< !std::is_class<T>{}, bool>::type = true
>
auto refl_tie( T const& t )
  RETURNS(std::tie(t))

template<class...Ts,
  typename std::enable_if< (sizeof...(Ts) > 1), bool>::type = true
>
auto refl_tie( Ts const&... ts )
  RETURNS(std::make_tuple(refl_tie(ts)...))

template<class T, std::size_t N>
auto refl_tie( T const(&t)[N] ) {
  // lots of work in C++11 to support this case, todo.
  // in C++17 I could just make a tie of each of the N elements of the array?

  // in C++11 I might write a custom struct that supports an array
  // reference/pointer of fixed size and implements =, ==, !=, <, etc.
}

struct foo {
  int x;
};
struct bar {
  foo f1, f2;
};
auto refl_tie( foo const& s )
  RETURNS( refl_tie( s.x ) )
auto refl_tie( bar const& s )
  RETURNS( refl_tie( s.f1, s.f2 ) )

refl_tie(array)(完全递归,甚至支持数组数组):

template<class T, std::size_t N, std::size_t...Is>
auto array_refl( T const(&t)[N], std::index_sequence<Is...> )
  RETURNS( std::array<decltype( refl_tie(t[0]) ), N>{ refl_tie( t[Is] )... } )

template<class T, std::size_t N>
auto refl_tie( T(&t)[N] )
  RETURNS( array_refl( t, std::make_index_sequence<N>{} ) )

活生生的例子

在这里,我使用std::array refl_tie 这比我之前编译时的 refl_tie 元组快得多。

template<class T,
  typename std::enable_if< !std::is_class<T>{}, bool>::type = true
>
auto refl_tie( T const& t )
  RETURNS(std::cref(t))

在这里使用std::cref而不是std::tie可以节省编译时开销,因为cref是一个比tuple简单得多的类。

最后,你应该添加

template<class T, std::size_t N, class...Ts>
auto refl_tie( T(&t)[N], Ts&&... ) = delete;

这将防止数组成员衰减为指针并返回指针相等(您可能不希望从数组中获得)。

如果没有这个,如果你将一个数组传递给一个非反射结构体,它会返回到指向非反射结构体refl_tie ,它可以工作并返回无意义的。

这样,您最终会遇到编译时错误。


通过库类型支持递归很棘手。 你可以std::tie它们:

template<class T, class A>
auto refl_tie( std::vector<T, A> const& v )
  RETURNS( std::tie(v) )

但这不支持通过它递归。

简而言之:不可能以通用的方式。

memcmp的问题在于填充可能包含任意数据,因此memcmp可能会失败。 如果有办法找出填充的位置,您可以将这些位清零,然后比较数据表示,如果成员是微不足道的,这将检查相等性(情况并非如此,即std::string因为两个字符串可以包含不同的指针,但指向的两个字符数组是相等的)。 但我知道没有办法获得结构的填充。 您可以尝试告诉您的编译器打包结构,但这会使访问变慢并且不能真正保证工作。

实现这一点的最简洁的方法是比较所有成员。 当然,这在通用方式中是不可能的(直到我们在 C++23 或更高版本中获得编译时反射和元类)。 从 C++20 开始,可以生成一个默认的operator<=>但我认为这也只能作为成员函数,所以,这又不是真的适用。 如果你很幸运,并且你想比较的所有结构都定义了一个operator== ,你当然可以使用它。 但这并不能保证。

编辑:好的,实际上有一种完全hacky且有点通用的聚合方式。 (我只写了对元组的转换,那些有一个默认的比较运算符)。 神箭

C++ 20 支持 默认comparisons

#include <iostream>
#include <compare>

struct XYZ
{
    int x;
    char y;
    long z;

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

int main()
{
    XYZ obj1 = {4,5,6};
    XYZ obj2 = {4,5,6};

    if (obj1 == obj2)
    {
        std::cout << "objects are identical\n";
    }
    else
    {
        std::cout << "objects are not identical\n";
    }
    return 0;
}

假设 POD 数据,默认赋值运算符仅复制成员字节。 (实际上不是 100% 确定,不要相信我的话)

您可以利用它来发挥自己的优势:

template<typename Data>
bool structCmp(Data data1, Data data2) // Data is POD
{
  Data tmp;
  memcpy(&tmp, &data1, sizeof(Data)); // copy data1 including padding
  tmp = data2;                        // copy data2 only members
  return memcmp(&tmp, &data1, sizeof(Data)) == 0; 
}

我相信您可以在magic_get库中基于 Antony Polukhin 奇妙狡猾的伏都教的解决方案 - 对于结构,而不是复杂类。

使用该库,我们能够在纯通用模板代码中使用适当的类型迭代结构的不同字段。 例如,Antony 已经使用它来将任意结构流式传输到具有正确类型的输出流,完全通用。 按理说,比较也可能是这种方法的一种可能应用。

...但你需要 C++14。 至少它比其他答案中的 C++17 和更高版本的建议要好:-P

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM