Почему я должен перегружать оператор == в типах POD? - PullRequest
15 голосов
/ 25 марта 2012

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

struct Vec3 {
float x, y, z;
}

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

Описание Расположение пути к ресурсу Тип не соответствует 'operator ==' в '_ first. _gnu_cxx :: __ normal_iterator <_Iterator, _Container> :: operator * с _Iterator = Vec3 *, _Container = std :: vector> == _ next. _gnu_cxx :: __ normal_iterator <_Iterator, _Container> :: operator * с _Iterator = Vec3 *, _Container = std :: vector> 'строка 4351 ModelConverter, внешнее расположение: / usr / include/c++/4.4.6/bits/stl_algo.h C / C ++ Задача

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

Ответы [ 3 ]

17 голосов
/ 25 марта 2012

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

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

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

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

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

5 голосов
/ 25 марта 2012

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

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

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

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

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

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

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

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

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

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

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

0 голосов
/ 25 марта 2012

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...