Почему мне приходится перегружать operator == в типы POD?

У меня есть структура, которая определяется следующим образом:

struct Vec3 {
float x, y, z;
}

Когда я попытался использовать std::unique на std::vector<Vec3>, мне была встречена эта ошибка:

Описание Тип расположения пути ресурса нет соответствия для 'operator == in' _first._gnu_cxx:: __ normal_iterator < _Iterator, _Container > :: operator * с _Iterator = Vec3 *, _Container = std::vector > == _next._gnu_cxx:: __ normal_iterator < _Iterator, _Container > :: оператор * с _Iterator = Vec3 *, _Container = std::vector > ModelConverter line 4351, внешнее местоположение:/usr/include/c++/4.4.6/bits/stl_algo.h Проблема C/С++

Я понимаю необходимость наивита компилятора в in операторах равенства и других (в этом случае * почти наверняка не будет того, что я имею в виду), но это вопрос политики, или есть техническая причина для этого, о которой я не знаю? Есть оператор присваивания по умолчанию, так почему же оператор равенства по умолчанию?

Ответ 1

Там нет технической причины. Педантично, вы можете сказать, что это связано с тем, что C не позволяет сравнивать две структуры с ==, и это хорошая причина; что переключение поведения при переходе на С++ неочевидно. (Предположительно, причина, по которой C не поддерживает, заключается в том, что полевое сравнение может работать для некоторых структур, но определенно не для всех.)

И только с точки зрения С++, что, если у вас есть личное поле? По умолчанию == технически раскрывает это поле (косвенно, но все же). Так будет ли компилятор генерировать operator==, если нет частных или защищенных данных?

Кроме того, существуют классы, которые не имеют разумного определения равенства (пустые классы, классы, которые не моделируют состояние, но кэшируют его и т.д.), или для которых проверка равенства по умолчанию может быть чрезвычайно запутанной (классы, которые переносят указатели).

А потом наследование. Решение о том, что делать для operator== в ситуации наследования, является сложным, и компилятор будет легко принять неправильное решение. (Например, если это то, что сделал С++, мы, вероятно, будем задавать вопросы о том, почему == всегда выполняется, когда вы проверяете равенство между двумя объектами, которые являются потомками абстрактного базового класса и используются со ссылкой на него.)

В принципе, это сложная проблема, и для компилятора это безопаснее не вмешиваться, даже если вы можете переопределить все, что решил компилятор.

Ответ 2

Вопрос о том, почему вы должны предоставить operator==, не совпадает с вопросом о том, почему вы должны предоставить некоторую функцию сравнения.

Что касается последнего, то причина, по которой вам требуется предоставить логику сравнения, заключается в том, что элементарное равенство редко подходит. Рассмотрим, например, структуру POD с массивом char. Если это используется для хранения строки с нулевым завершением, то две такие структуры могут сравниваться неравномерно на двоичном уровне (из-за произвольного содержимого после нулевых байтов в строках), но все же логически эквивалентны.

Кроме того, есть все сложности уровня С++, упомянутые в других ответах здесь, например. особенно тернистый из полиморфного равенства (вы действительно не хотите, чтобы компилятор выбирал!).

Итак, по сути, просто нет хорошего выбора по умолчанию, поэтому выбор за вами.

Относительно первого вопроса, который вы буквально задали, почему вы должны предоставить operator==?

Если вы определяете operator< и operator==, то определения операторов в пространстве имен std::rel_ops могут заполнить для вас остальное. Предположительно, причина, по которой требуется operator==, заключается в том, что было бы бесполезно неэффективно реализовывать ее в терминах operator< (тогда требуются два сравнения). Однако выбор этих двух операторов в качестве основы является полностью непонятным, поскольку он делает код пользователя многословным и сложным, а в некоторых случаях гораздо менее эффективным, чем возможно!

Лучшей основой ИМХО для операторов сравнения является трехзначная функция compare, такая как std::string::compare.

Учитывая вариант функции-члена comparedTo, вы можете использовать класс Curiously Recurring Template Pattern, подобный приведенному ниже, чтобы предоставить полный набор операторов:

template< class Derived >
class ComparisionOps
{
public:
    friend int compare( Derived const a, Derived const& b )
    {
        return a.comparedTo( b );
    }

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

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

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

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

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

    friend bool operator!=( Derived const a, Derived const b )
    {
        return (compare( a, b ) != 0);
    }
};

где compare является перегруженной функцией, например. например:

template< class Type >
inline bool lt( Type const& a, Type const& b )
{
    return std::less<Type>()( a, b );
}

template< class Type >
inline bool eq( Type const& a, Type const& b )
{
    return std::equal_to<Type>()( a, b );
}

template< class Type >
inline int compare( Type const& a, Type const b )
{
    return (lt( a, b )? -1 : eq( a, b )? 0 : +1);
}

template< class Char >
inline int compare( basic_string<Char> const& a, basic_string<Char> const& b )
{
    return a.compare( b );
}

template< class Char >
inline int compareCStrings( Char const a[], Char const b[] )
{
    typedef char_traits<Char>   Traits;

    Size const  aLen    = Traits::length( a );
    Size const  bLen    = Traits::length( b );

    // Since there can be negative Char values, cannot rely on comparision stopping
    // at zero termination (this can probably be much optimized at assembly level):
    int const way = Traits::compare( a, b, min( aLen, bLen ) );
    return (way == 0? compare( aLen, bLen ) : way);
}

inline int compare( char const a[], char const b[] )
{
    return compareCStrings( a, b );
}

inline int compare( wchar_t const a[], wchar_t const b[] )
{
    return compareCStrings( a, b );
}

Теперь, это машина. Как это выглядит, чтобы применить его к вашему классу & hellip;

struct Vec3
{
    float x, y, z;
};

?

Хорошо, это довольно просто:

struct Vec3
    : public ComparisionOps<Vec3>
{
    float x, y, z;

    int comparedTo( Vec3 const& other ) const
    {
        if( int c = compare( x, other.x ) ) { return c; }
        if( int c = compare( y, other.y ) ) { return c; }
        if( int c = compare( z, other.z ) ) { return c; }
        return 0;   // Equal.
    }
};

Отказ от ответственности: не очень проверенный код & hellip;: -)

Ответ 3

Что бы вы хотели сделать операцией равенства? Все поля одинаковые? Это не сделает это для вас.