Ответ 1
Что касается предпосылки memcmp
, дающей тот же результат, что и членские сравнения с ==
, хотя это предположение часто выполняется на практике, оно несколько хрупкое.
Изменение компиляторов или параметров компилятора может теоретически нарушить это предварительное условие. Более того, обслуживание кода (и 80% всей работы по программированию - это обслуживание, IIRC) может сломать его, добавив или удалив участников, сделав класс полиморфным, добавив пользовательские перегрузки ==
и т.д. И, как упоминалось в одном из комментариев, предварительное условие может сохраняться для статических переменных, в то время как оно не выполняется для автоматических переменных, а затем работа по обслуживанию, которая создает нестатические объекты, может делать Bad Things & trade;.
И в связи с вопросом о том, следует ли использовать memcmp
или member-wise ==
для реализации оператора ==
для класса, во-первых, это ложная дихотомия, поскольку это не единственные варианты.
Например, при использовании функции 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
или ==
:
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 )
{}
};
Как указано, код клиента, например, >
требуется a 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 )
{}
};
Что выбрать.
Учитывая список возможных аспектов для оценки большинства, таких как безопасность, ясность, эффективность, краткость, оцените каждый подход выше.
Затем выберите ту, которая вам лучше всего подходит, или один из подходов, которые кажутся примерно одинаковыми.
Руководство. Для безопасности вы не захотите выбирать подход A, вычитание, поскольку оно опирается на предположение о значениях. Обратите внимание, что также опция B, memcmp
, небезопасна как реализация для общего случая, но может преуспеть для всего лишь ==
и !=
. Для эффективности вы должны лучше MEASURE, с соответствующими параметрами компилятора и средой, а также помните поговорку Дональда Кнута: "преждевременная оптимизация - это корень всего зла" (т.е. Тратить время на то, что может быть контрпродуктивным).