当两者都给出相同的结果时,我应该使用memcmp或链式等于操作吗?

时间:2015-03-04 15:31:23

标签: c++ memcmp

前提条件:考虑这样的类或结构T,对于类型为a的两个对象bT

memcmp(&a, &b, sizeof(T)) == 0

产生与

相同的结果
a.member1 == b.member1 && a.member2 == b.member2 && ...

memberNT)的非静态成员变量。

问题:何时应memcmp用于比较ab的相等性,何时应使用链式==


这是一个简单的例子:

struct vector
{
    int x, y;
};

要为==重载运营商vector,有两种可能性(如果他们确保提供相同的结果):

bool operator==(vector lhs, vector rhs)
{ return lhs.x == rhs.x && lhs.y == rhs.y; }

bool operator==(vector lhs, vector rhs)
{ return memcmp(&lhs, &rhs, sizeof(vector)) == 0; }

现在,如果要将新成员添加到vector,例如z组件:

  • 如果==用于实施operator==,则必须进行修改。
  • 如果使用memcmp,则operator==根本不需要修改。

但我认为使用链式==会传达更清晰的含义。虽然对于有很多成员T的大型memcmp来说更具吸引力。此外,使用memcmp优于== s是否有提升效果?还有什么需要考虑的吗?

6 个答案:

答案 0 :(得分:16)

关于memcmp产生与==成员比较相同结果的前提条件,虽然这个前提条件在实践中经常得到满足,但它有些脆弱

改变编译器或编译器选项理论上可以打破这个前提条件。更值得关注的是,代码维护(以及80%的编程工作是维护,IIRC)可以通过添加或删除成员,使类具有多态性,添加自定义==重载等来打破它。如上所述注释,前置条件可以保留静态变量而不适用于自动变量,然后创建非静态对象的维护工作可以执行Bad Things™。

关于是否使用memcmp或成员==来为班级实施==运算符的问题,首先,这是一个错误的二分法,因为那些不是唯一的选择。

例如,就compare函数而言,使用自动生成关系运算符重载可以减少工作量和维护性。 std::string::compare函数就是这种函数的一个例子。

其次,选择什么实现的答案很大程度上取决于您认为重要的内容,例如:

  • 应该寻求最大化运行时效率,或者

  • 应该寻求创建最清晰的代码,或者

  • 应该寻求最简洁,最快写代码,或者

  • 应该设法使课程最安全使用,或者

  • 其他可能吗?

生成关系运算符。

您可能听说过CRTP,奇怪的重复模板模式。我记得它是为了处理生成关系运算符重载的要求而发明的。不过,我可能会将其与其他东西混为一谈,但无论如何:

template< class Derived >
struct Relops_from_compare
{
    friend
    auto operator!=( const Derived& a, const Derived& b )
        -> bool
    { return compare( a, b ) != 0; }

    friend
    auto operator<( const Derived& a, const Derived& b )
        -> bool
    { return compare( a, b ) < 0; }

    friend
    auto operator<=( const Derived& a, const Derived& b )
        -> bool
    { return compare( a, b ) <= 0; }

    friend
    auto operator==( const Derived& a, const Derived& b )
        -> bool
    { return compare( a, b ) == 0; }

    friend
    auto operator>=( const Derived& a, const Derived& b )
        -> bool
    { return compare( a, b ) >= 0; }

    friend
    auto operator>( const Derived& a, const Derived& b )
        -> bool
    { return compare( a, b ) > 0; }
};

鉴于上述支持,我们可以调查您的问题的可用选项。

实施A:通过减法进行比较。

这是一个提供全套关系运算符而不使用memcmp==的类:

struct Vector
    : Relops_from_compare< Vector >
{
    int x, y, z;

    // This implementation assumes no overflow occurs.
    friend
    auto compare( const Vector& a, const Vector& b )
        -> int
    {
        if( const auto r = a.x - b.x ) { return r; }
        if( const auto r = a.y - b.y ) { return r; }
        return a.z - b.z;
    }

    Vector( const int _x, const int _y, const int _z )
        : x( _x ), y( _y ), z( _z )
    {}
};

实施B:通过memcmp进行比较。

这是使用memcmp实现的同一个类;我想你会同意这个代码更好,更简单:

struct Vector
    : Relops_from_compare< Vector >
{
    int x, y, z;

    // This implementation requires that there is no padding.
    // Also, it doesn't deal with negative numbers for < or >.
    friend
    auto compare( const Vector& a, const Vector& b )
        -> int
    {
        static_assert( sizeof( Vector ) == 3*sizeof( x ), "!" );
        return memcmp( &a, &b, sizeof( Vector ) );
    }

    Vector( const int _x, const int _y, const int _z )
        : x( _x ), y( _y ), z( _z )
    {}
};

实施C:按成员比较成员。

这是使用成员比较的实现。它没有强加任何特殊要求或假设。但它的源代码更多。

struct Vector
    : Relops_from_compare< Vector >
{
    int x, y, z;

    friend
    auto compare( const Vector& a, const Vector& b )
        -> int
    {
        if( a.x < b.x ) { return -1; }
        if( a.x > b.x ) { return +1; }
        if( a.y < b.y ) { return -1; }
        if( a.y > b.y ) { return +1; }
        if( a.z < b.z ) { return -1; }
        if( a.z > b.z ) { return +1; }
        return 0;
    }

    Vector( const int _x, const int _y, const int _z )
        : x( _x ), y( _y ), z( _z )
    {}
};

在关系运算符方面实现D:compare

这是一种颠倒事物自然顺序的实现方式,通过compare<实现==,它们是直接提供的,并以{{1}的形式实现比较(使用std::tuple)。

std::tie

如上所述,客户代码使用例如struct Vector { int x, y, z; friend auto operator<( const Vector& a, const Vector& b ) -> bool { using std::tie; return tie( a.x, a.y, a.z ) < tie( b.x, b.y, b.z ); } friend auto operator==( const Vector& a, const Vector& b ) -> bool { using std::tie; return tie( a.x, a.y, a.z ) == tie( b.x, b.y, b.z ); } friend auto compare( const Vector& a, const Vector& b ) -> int { return (a < b? -1 : a == b? 0 : +1); } Vector( const int _x, const int _y, const int _z ) : x( _x ), y( _y ), z( _z ) {} }; 需要>

替代方案包括将所有其他运算符添加到上面(更多代码),或者使用CRTP运算符生成方案,该方案根据using namespace std::rel_ops;<实现其他运算符(可能效率低下)。 / p>

实施E:通过手动使用=<进行比较。

这个实现是不应用任何抽象的结果,只是敲击键盘并直接写出机器应该做的事情:

==

选择什么。

考虑最重要的可能方面的清单,如安全性,清晰度,效率,简洁性,评估上述每种方法。

然后选择对你来说最明显的那个,或者看起来同样最好的方法之一。

指导:为了安全起见,您不希望选择方法A,减法,因为它依赖于关于值的假设。请注意,选项B struct Vector { int x, y, z; friend auto operator<( const Vector& a, const Vector& b ) -> bool { return ( a.x < b.x || a.x == b.x && ( a.y < b.y || a.y == b.y && ( a.z < b.z ) ) ); } friend auto operator==( const Vector& a, const Vector& b ) -> bool { return a.x == b.x && a.y == b.y && a.z == b.z; } friend auto compare( const Vector& a, const Vector& b ) -> int { return (a < b? -1 : a == b? 0 : +1); } Vector( const int _x, const int _y, const int _z ) : x( _x ), y( _y ), z( _z ) {} }; 作为一般情况的实现也不安全,但仅适用于memcmp==。为了提高效率,您应该更好地 MEASURE ,使用相关的编译器选项和环境,并记住Donald Knuth的谚语:“过早优化是所有邪恶的根源”(即花费时间可能会适得其反)。

答案 1 :(得分:12)

如果正如你所说的那样,你选择的类型使得两种解决方案产生相同的结果(大概是,你没有间接数据,并且对齐/填充是完全相同的),那么显然你可以使用哪一种你喜欢的解决方案。

需要考虑的事项:

  1. 效果:我怀疑你会看到多少差异,但衡量以确保,如果你关心;
  2. 安全性嗯,您说T的两种解决方案是相同的,但它们是什么?他们真的吗?在所有系统上?您的memcmp方法是否可移植?可能不是;
  3. 清晰度:如果您的前提条件发生变化并且您没有充分评论 - 描述您的memcmp用法,那么您的计划可能会被破坏 - 因此您已将其变得脆弱; < / LI>
  4. 一致性:据推测,您可以在其他地方使用==;当然,你必须为不符合你前提条件的每一个T做这件事;除非这是T的故意优化专业化,否则您可以考虑在整个计划中坚持使用单一方法;
  5. 易于使用:当然,很容易错过链接==的成员,特别是如果您的成员列表不断增长。

答案 2 :(得分:6)

如果两个解决方案都正确,则更喜欢更易读的解决方案。我要说的是,对于C ++程序员来说,==memcmp更具可读性。我甚至会使用std::tie而不是链接:

bool operator==(const vector &lhs, const vector &rhs)
{ return std::tie(lhs.x, lhs.y) == std::tie(rhs.x, rhs.y); }

答案 3 :(得分:4)

如果只有结构是POD并且它是安全memcmp可比的(甚至不是所有数字类型都是......),结果是相同的,并且问题是关于可读性和性能。

可读性?我认为这是一个基于意见的问题,但我更喜欢operator==

效果? operator==是短路运营商。您可以在此处更好地控制您的程序,因为您可以重新排序比较序列。

尽管a == b && c == dc == d && a == b在算法逻辑方面是相同的(结果是相同的),但它们在生成的程序集,“后台逻辑”和可能的性能方面并不等效。

如果你能预见到一些观点,你可以影响你的计划。

例如:

  • 如果两个陈述大致同样可能产生错误,那么如果可能的话,你会希望先用更便宜的陈述跳过更复杂的比较。
  • 如果两个陈述大致相同,并且您事先知道c == da == b更可能错误,则应首先比较cd

可以使用operator==以与问题相关的方式调整比较序列,而memcmp不会给您这种自由。

PS:你想要测量它,但对于一个有3个成员的小结构,MS VS 2013为memcmp情况产生稍微复杂的程序集。在这种情况下,我希望operator==解决方案具有更高的性能(如果影响是可测量的)。

- /伊迪丝 -

注意:即使是POD结构成员也可能超载operator==

考虑:

#include <iostream>
#include <iomanip>

struct A { int * p; };

bool operator== (A const &a, A const &b) { return *(a.p) == *(b.p); }

struct B { A m; };

bool operator== (B const &a, B const &b) { return a.m == b.m; }

int main()
{
  int a(1), b(1);
  B x, y;
  x.m.p = &a;
  y.m.p = &b;
  std::cout << std::boolalpha;
  std::cout << (memcmp(&x, &y, sizeof(B)) == 0) << "\n";
  std::cout << (x == y) << "\n";
  return 0;
}

打印

false
true

即使-in turn-所有成员都是基本类型我更喜欢operator==并留给编译器考虑优化比较到它认为更好的任何程序集。

答案 4 :(得分:1)

你施加了一个非常强大的条件,即没有填充(我不假设在类的成员之间,也不在这些成员内部)。我认为你也打算排除任何隐藏的&#34;来自班级的家政数据。此外,问题本身意味着我们总是比较完全相同类型的对象。在如此强大的条件下,可能无法提出一个反例,使基于memcmp的比较与==比较不同。

出于性能原因,是否值得使用memcmp ......好吧,如果你真的有充分的理由积极优化一些关键代码并且分析表明它有改进之后从==切换到memcmp,然后肯定会继续。但即使你的班级满足要求,我也不会将它作为编写比较运算符的常规技术使用。

答案 5 :(得分:1)

==更好,因为memcmp比较纯内存数据(比较这种方式在许多情况下可能是错误的,例如std::string,数组模仿类或类型可以相等即使它们不完全相同)。由于在类中可能存在这样的类型,因此应始终使用自己的运算符而不是比较原始内存数据。

==也更好,因为它比一些奇怪的功能更具可读性。

相关问题