我应该使用memcmp还是链接的等于操作,当两者给出相同的结果? [英] Should I use memcmp or chained equal-to operations when both give the same result?

查看:127
本文介绍了我应该使用memcmp还是链接的等于操作,当两者给出相同的结果?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

产生与

相同的结果。

  a.member1 == b.member1&& a.member2 == bmember2&&& ... 

memberN T 的静态成员变量。



问题:何时应该 memcmp 用于比较 a b 使用 ==






示例:

  struct vector 
{
int x,y;
};

要重载运算符 == c $ c> 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; }






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




  • 如果 == s用于实现 operator == ,则必须修改

  • 如果使用 memcmp operator ==


但我认为使用链接 == s表示更清楚的意思。虽然对于许多成员 memcmp 的大的 T 是更诱人的。此外,使用 memcmp == s有什么性能改进吗?

解决方案

关于 memcmp 的前提条件与 == 的成员比较相同的结果,而这个前提通常在实践中被满足,它有点



更改编译器或编译器选项在理论上可以破坏前提条件。更关心的是,代码维护(和所有编程工作的80%是维护,IIRC)可以通过添加或删除成员来破坏它,使类多态,添加自定义 == 重载等。如前所述,前提条件可以适用于静态变量,而不适用于自动变量,然后创建非静态对象的维护工作可以做坏事和交易。



关于是否使用 memcmp 或成员 == 为类实现一个 == 运算符,首先,这是一个假二分法,因为那些不是唯一的选项。



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



其次,什么实现选择取决于您认为重要的,例如:




  • / strong>或


  • 时,请尝试创建最清晰的代码 b

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


  • 安全以使用


  • 还是其他类别?




生成关系运算符。



您可能听说过CRTP, / em>。我记得它被发明来处理生成关系运算符重载的需求。我可能会与其他的东西conflating,但无论如何,

  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<矢量>
{
int x,y,z;

//这个实现假设没有发生溢出。
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 {}
};实现B:通过 memcmp 进行比较。


$ b

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

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

//这个实现需要没有填充。
//另外,它不处理<或> ;.
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 {}
};



实施C:成员比较成员。



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

  struct Vector 
:Relops_from_compare<矢量>
{
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 if(a.z> b.z){return +1; }
return 0;
}

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



执行D:比较运算符



这是一种颠倒事物自然顺序的实现类型,通过实现 compare code>< 和 == ,直接提供并以 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 {}
};

> 需要使用命名空间std :: rel_ops; 。



替代方法包括将所有其他运算符添加到上述代码(更多的代码),或使用CRTP运算符生成方案实现< 实施E:通过手动使用< 比较 / code>和 ==

此实现是不应用任何抽象的结果,在键盘上直接书写机器应该做什么:

  struct Vector 
{
int x ,y,z;

friend
auto operator<(const Vector& a,const Vector& b)
- > bool
{
return(
ax< bx ||
ax == bx&&(
ay< by ||
ay) ==由&&(
az< bz


);
}

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 {}
};



选择什么。





然后选择一个最适合你的方法,或者一个最好的方法,例如安全性,清晰度,效率,的方法看起来同样最好。



指导:为了安全,你不想选择方法A,减法,因为它依赖于一个关于值的假设。注意,选项B memcmp 作为一般情况的实现是不安全的,但对于 == != 。为了提高效率,您应该使用相关的编译器选项和环境更好地 MEASURE ,并记住Donald Knuth的格言:过早优化是所有邪恶的根源 (即花费时间可能会产生反效果)。


Precondition: Consider such a class or struct T, that for two objects a and b of type T

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

yields the same result as

a.member1 == b.member1 && a.member2 == b.member2 && ...

(memberN is a non-static member variable of T).

Question: When should memcmp be used to compare a and b for equality, and when should the chained ==s be used?


Here's a simple example:

struct vector
{
    int x, y;
};

To overload operator == for vector, there are two possibilities (if they're guaranteed to give the same result):

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

or

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


Now if a new member were to be added to vector, for example a z component:

  • If ==s were used to implement operator==, it would have to be modified.
  • If memcmp was used instead, operator== wouldn't have to be modified at all.

But I think using chained ==s conveys a clearer meaning. Although for a large T with many members memcmp is more tempting. Additionally, is there a performance improvement from using memcmp over ==s? Anything else to consider?

解决方案

Regarding the precondition of memcmp yielding the same result as member-wise comparisons with ==, while this precondition is often fulfilled in practice, it's somewhat brittle.

Changing compilers or compiler options can in theory break that precondition. Of more concern, code maintenance (and 80% of all programming work is maintenance, IIRC) can break it by adding or removing members, making the class polymorphic, adding custom == overloads, etc. And as mentioned in one of the comments, the precondition can hold for static variables while it doesn't hold for automatic variables, and then maintenance work that creates non-static objects can do Bad Things™.

And regarding the question of whether to use memcmp or member-wise == to implement an == operator for the class, first, this is a false dichotomy, for those are not the only options.

For example, it can be less work and more maintainable to use automatic generation of relational operator overloads, in terms of a compare function. The std::string::compare function is an example of such a function.

Secondly, the answer to what implementation to choose depends strongly on what you consider important, e.g.:

  • should one seek to maximize runtime efficiency, or

  • should one seek to create clearest code, or

  • should one seek the most terse, fastest to write code, or

  • should one seek to make the class most safe to use, or

  • something else, perhaps?

Generating relational operators.

You may have heard of CRTP, the Curiously Recurring Template Pattern. As I recall it was invented to deal with the requirement of generating relational operator overloads. I may possibly be conflating that with something else, though, but anyway:

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; }
};

Given the above support, we can investigate the options available for your question.

Implementation A: comparison by subtraction.

This is a class providing a full set of relational operators without using either memcmp or ==:

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 )
    {}
};

Implementation B: comparison via memcmp.

This is the same class implemented using memcmp; I think you'll agree that this code scales better and is simpler:

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 )
    {}
};

Implementation C: comparison member by member.

This is an implementation using member-wise comparisons. It doesn't impose any special requirements or assumptions. But it's more source code.

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 )
    {}
};

Implementation D: compare in terms of relational operators.

This is an implementation sort of reversing the natural order of things, by implementing compare in terms of < and ==, which are provided directly and implemented in terms of std::tuple comparisons (using 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 )
    {}
};

As given, client code using e.g. > needs a using namespace std::rel_ops;.

Alternatives include adding all other operators to the above (much more code), or using a CRTP operator generation scheme that implements the other operators in terms of < and = (possibly inefficiently).

Implementation E: comparision by manual use of < and ==.

This implementation is the result not applying any abstraction, just banging away at the keyboard and writing directly what the machine should do:

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 )
    {}
};

What to choose.

Considering the list of possible aspects to value most, like safety, clarity, efficiency, shortness, evaluate each approach above.

Then choose the one that to you is clearly best, or one of the approaches that seem about equally best.

Guidance: For safety you would not want to choose approach A, subtraction, since it relies on an assumption about the values. Note that also option B, memcmp, is unsafe as an implementation for the general case, but can do well for just == and !=. For efficiency you should better MEASURE, with relevant compiler options and environment, and remember Donald Knuth's adage: “premature optimization is the root of all evil” (i.e. spending time on that may be counter-productive).

这篇关于我应该使用memcmp还是链接的等于操作,当两者给出相同的结果?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆