C++ 不带运算符的类相等性检查==

C++ 不带运算符的类相等性检查==,c++,c++11,C++,C++11,我有一个非常大的数据模型,其中有许多成员,其中许多成员本身就是大型数据模型,有几个层次的嵌套。顶级类表示序列化并发送到服务器进行备份的整体模型。作为调试步骤,我们希望对最近的备份进行反序列化,并将其与备份时的内存中数据模型进行比较,后者应该相等。最明显的方法是对当前模型及其序列化后反序列化的版本应用操作符== 问题是嵌套的程度和定制数据结构的数量需要大量的代码来编写所有这些operator==实现。更不用说,这些单独的实现中的许多都需要很多行来比较每个成员的平等性。我们很容易就谈到了刚刚花在操作

我有一个非常大的数据模型,其中有许多成员,其中许多成员本身就是大型数据模型,有几个层次的嵌套。顶级类表示序列化并发送到服务器进行备份的整体模型。作为调试步骤,我们希望对最近的备份进行反序列化,并将其与备份时的内存中数据模型进行比较,后者应该相等。最明显的方法是对当前模型及其序列化后反序列化的版本应用
操作符==

问题是嵌套的程度和定制数据结构的数量需要大量的代码来编写所有这些
operator==
实现。更不用说,这些单独的实现中的许多都需要很多行来比较每个成员的平等性。我们很容易就谈到了刚刚花在
操作符==
上的>1k行代码。即使我们做了所有这些,在类似这样的事情上,程序员错误的空间还是很大的


有没有其他方法可以进行快速、肮脏(尽管可靠)的相等性检查,也许可以使用级别低得多的技术,或者不需要几天什么都不做,只需要编写
操作符==
函数的任何方法?

最好的选择是
tie
解决方案

struct equal_by_tie {
  template<class T>
  using enable = std::enable_if_t<std::is_base_of<equal_by_tie, T>,bool>;
  template<class T>
  friend enable<T>
  operator==( T const& lhs, T const& rhs ) {
    return mytie(lhs) == mytie(rhs);
  }
  template<class T>
  friend enable<T>
  operator!=( T const& lhs, T const& rhs ) {
    return mytie(lhs) != mytie(rhs);
  }
};
==
=是为您编写的

目前没有办法审核mytie是否正确编写,除了C++17中的一些不值得考虑的黑客行为(结构化绑定,这是一种可怕的黑客行为,不问)

减少mytie出现错误的一种方法是更多地使用它

在it方面实现
swap
(可能使用与上面的
operator==
相同的父类技巧)。现在在
swap
mytie
方面实现
operator=
。对
friend std::size\u t hash(Foo const&)
执行同样的操作,并将其挂接到标准hasher中

坚持
mytie
与数据声明的顺序相同,并将父实例作为子关系进行绑定。编写一个函数,将系统结构/类对齐考虑在内,并计算结构在
constexpr
中的大小。静态断言
Foo
calc\u struct\u size(tag)
的大小匹配。(根据需要为vtables或类似内容添加模糊因子)。现在,在不接触mytie的情况下更改结构的布局会导致糟糕的事情发生

比较
mytie
中的每对字段的指针不相等性,以确保不会重复同一字段两次;尝试确保在运行时将其优化为
true
(需要技巧,因为您需要在调试中执行此签入操作,并且调试通常会关闭优化;这可能是您只希望在发布版本中执行的断言的一种独特情况!)

你还需要做一些健康检查。如果您的
mytie
包含原始指针,
==
是错误的,对于智能指针也是错误的;您希望您的
==
是一个深度平等

为此,也许
==
是错误的

struct deep\u equal\u by\u tie{
模板
使用enable=std::enable\u if\u t;
模板
朋友使能
深度相等(T常量和左侧、T常量和右侧){
//在每个领带上调用deep_equal的代码
//定义为的非指针基本类型上的deep_equal==
//指针上的deep_equal用于检查空值(空值相等)
//然后解引用和深_相等
//智能指针也是如此
//向量和其他std容器上的deep_equal用于检查尺寸,
//如果匹配元素上的deep_等于
}
};
但是,这会增加您的负载。但是这个想法是为了提高可靠性,正如您所注意到的,困难的部分是有很多代码和很多错误点

要做到这一点没有简单的方法


如果您的数据不是完全打包的纯旧数据,没有指针或虚拟函数或任何东西,那么memcmp是一个坏主意。而且填充很容易滑入代码,打破了基于memcmp的平等性;由于填充中的数据状态未定义,因此很难找到这样的braeks。

如果您考虑到这一点,我不会真正推荐
memcmp()
。您不能将内存中的数据转储到一个文件中,然后执行
diff
?@Rakete1111如果我知道怎么做的话,也许我可以。这就是我问这个问题的原因。请注意,如果您不这样做:您可以使用
std::tie
使成员比较运算符更容易。你可以使用
return std::tie(mem1,mem2,mem3,mem4,…)==std::tie(other.mem1,other.mem2,other.mem3,other.mem4,…)
@NathanOliver我知道这个技巧,但那仍然是一大堆代码,tie中的这些参数列表将是巨大的。当然,还需要重写tie的序列化,因为小马。@n.m.这使得
=
检查在这一点上有点可笑,因为两者都是使用相同的系统编写的。但是,我上面提到的疯狂检查可以帮助你。。。
struct some_thing : equal_by_tie {
public:
  friend auto mytie( some_thing const& self ) {
    return std::tie( self.x, self.y, self.mem3 );
  }
};
struct deep_equal_by_tie {
  template<class T>
  using enable = std::enable_if_t<std::is_base_of<equal_by_tie, T>,bool>;
  template<class T>
  friend enable<T>
  deep_equal( T const& lhs, T const& rhs ) {
    // code to call deep_equal on each tie
    // deep_equal on non-pointer basic types defined as ==
    // deep_equal on pointers is to check for null (nulls are equal)
    // then dereference and deep_equal
    // ditto for smart pointers
    // deep_equal on vectors and other std containers is to check size,
    // and if matches deep_equal on elements
  }
};