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 の格言を思い出してください。