24

前提条件: そのようなクラスまたは構造体Tを検討してくださいabT

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

と同じ結果が得られます

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

(memberNは の非静的メンバー変数ですT)。

質問:memcmpを比較aおよびb等価のために使用する必要がある==のはいつですか。


簡単な例を次に示します。

struct vector
{
    int x, y;
};

の演算子をオーバーロード==するvectorには、2 つの可能性があります (同じ結果が得られることが保証されている場合)。

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==、まったく変更する必要はありません。

==しかし、連鎖したs を使用すると、より明確な意味が伝わると思います。T多くのメンバーがいる大規模な場合は、memcmpより魅力的ですが。memcmpさらに、 overを使用することでパフォーマンスが向上し==ますか? 他に考慮すべきことはありますか?

4

6 に答える 6

17

memcmpとのメンバーごとの比較と同じ結果が得られるという前提条件については==、実際にはこの前提条件が満たされることが多いのですが、やや脆弱です。

コンパイラまたはコンパイラ オプションを変更すると、理論的にはその前提条件が崩れる可能性があります。さらに懸念されるのは、コードのメンテナンス (およびすべてのプログラミング作業の 80% がメンテナンス、IIRC) は、メンバーの追加または削除、クラスのポリモーフィック化、カスタム==オーバーロードの追加などによってコードを壊す可能性があることです。コメントの 1 つで述べたように、前提条件静的変数は保持できますが、自動変数は保持できず、非静的オブジェクトを作成するメンテナンス作業は悪いこと™ を行う可能性があります。

memcmpそして、クラスの演算子を実装するために使用するか、メンバーごと==に実装するかという問題に関しては==、まず、これは誤った二分法です。これらが唯一のオプションではないからです。

たとえば、関数に関しては、関係演算子オーバーロードの自動生成compareを使用する方が作業が少なく、保守しやすい場合があります。std::string::compare関数は、そのような関数の例です。

次に、どの実装を選択するかの答えは、何を重要と考えるかによって大きく異なります。たとえば、次のようになります。

  • ランタイム効率を最大化しようとする必要があるか、または

  • 最も明確なコードを作成しようとするか、または

  • コードを書くのに最も簡潔で最速を求めるか、または

  • クラスを最も安全に使用できるようにする必要があるか、または

  • おそらく何か他のものですか?

関係演算子の生成。

CRTP ( Curiously Recurring Template Pattern ) について聞いたことがあるかもしれません。私が思い出したように、関係演算子のオーバーロードを生成するという要件に対処するために発明されました。私はおそらくそれを他の何かと混同しているかもしれませんが、とにかく:

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これは、 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 )
    {}
};

実装 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これは、 との観点から実装することにより、物事の自然な順序を逆転させるような実装で<あり==、これらは直接提供され、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 )
    {}
};

与えられたように、eg を使用するクライアント コード>にはusing namespace std::rel_ops;.

代替手段には、他のすべての演算子を上記に追加する (はるかに多くのコード) か、<および=(おそらく非効率的に) に関して他の演算子を実装する CRTP 演算子生成スキームを使用することが含まれます。

<実装 E:との手動使用による比較==

この実装は、抽象化を適用せず、キーボードを叩いてマシンが何をすべきかを直接記述した結果です。

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

何を選ぶか。

安全性、明快さ、効率性、短さなど、最も重視する可能性のある側面のリストを考慮して、上記の各アプローチを評価します。

次に、自分にとって明らかに最適な方法、または同等に最適と思われる方法の 1 つを選択します。

ガイダンス: 安全のため、アプローチ A の減算は選択しないでください。これは、値に関する仮定に依存しているためです。オプション B, もmemcmp、一般的なケースの実装としては安全ではありませんが、 と だけではうまくいくことに==注意してください!=。効率を高めるには、適切なコンパイラ オプションと環境を使用してMEASUREを実行し、Donald Knuth の格言を思い出してください。

于 2015-03-04T17:48:55.357 に答える
12

あなたが言うように、2つのソリューションが同じ結果をもたらすようなタイプを選択した場合(おそらく、間接データがなく、配置/パディングはすべて同じです)、明らかに、好きなソリューションを使用できます.

考慮事項:

  1. パフォーマンス:多少の違いがあるとは思えませんが、気になる場合は測定してください。
  2. 安全性: 2 つのソリューションは同じものだとおっしゃっていますTが、そうでしょうか? 彼らは本当にですか?すべてのシステムで?あなたのmemcmpアプローチは移植可能ですか?おそらくそうではありません。
  3. 明快さ:前提条件が変更され、使用法を適切にコメントで説明していない場合memcmp、プログラムは破損する可能性があります。したがって、プログラムは壊れやすくなっています。
  4. 一貫性:おそらく他の場所で使用し==ます。T確かに、前提条件を満たしていないすべてに対してそれを行う必要があります。これが の意図的な最適化の専門化でない限りT、プログラム全体で単一のアプローチに固執することを検討してください。
  5. 使いやすさ:もちろん、 chained からメンバーを見逃すのは非常に簡単==です。特に、メンバーのリストが大きくなった場合はなおさらです。
于 2015-03-04T15:38:28.327 に答える
6

2 つの解決策が両方とも正しい場合は、より読みやすい方を優先してください。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); }
于 2015-03-04T15:40:25.243 に答える
1

==memcmp純粋なメモリデータを比較するため、より優れています(std::string完全に同一でなくても等しい可能性のある配列模倣クラスや型など、多くの状況でその方法を比較すると間違っている可能性があります)。クラス内にはそのような型が存在する可能性があるため、生のメモリ データを比較する代わりに、常に独自の演算子を使用する必要があります。

==奇妙に見える関数よりも読みやすいため、優れています。

于 2015-03-04T15:56:48.927 に答える