Std:: set имеет двойную запись

У меня есть набор кортежей из 3 целых чисел, и я не хочу дубликатов. То есть, я не хочу 2 записи с одинаковыми 3 значениями.

И вот мой код.

struct Key{
    unsigned a;
    unsigned b;
    unsigned c;
  public:
    Key(unsigned _a, unsigned _b, unsigned _c) :
        a(_a),
        b(_b),
        c(_c) {}
    bool operator<(const Key& rhs) const
    {
        if (a < rhs.a) {
            return true;
        }
        if (b < rhs.b) {
            return true;
        }
        if (c < rhs.c) {
            return true;
        }
        return false;
    };
};
std::set<Key> myset;

Но иногда я вижу дубликаты в myset. Я не могу точно понять, какая последовательность приводит к добавлению дублирующей записи. Это не всегда происходит. Мой вопрос в том, есть ли что-то внутренне неправильное с моей функцией operator<?

Ответы

Ответ 1

Это почти правильно! Но вы слишком быстро каскадируете.

bool operator<(const Key& rhs) const
{
    if (a < rhs.a)
        return true;
    if (a > rhs.a)
        return false;

    if (b < rhs.b)
        return true;
    if (b > rhs.b)
        return false;

    return (c < rhs.c);
};

В противном случае, например, следующий результат дает неправильный результат:

Key lhs{3,1,0};
Key rhs{2,2,0};

assert(lhs < rhs); // passes, wrongly, because !(3 < 2) but then (1 < 2).
                   // you need an immediate `return false` when !(3 < 2)

Безопаснее сделать что-то вроде этого:

bool operator<(const Key& rhs) const
{
    return std::tie(a, b, c) < std::tie(rhs.a, rhs.b, rhs.c);
}

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


Теперь, как ваша ошибка приведет к дублированию ключей в set?

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

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

Неудивительно пытаться рассуждать о конкретной цепочке событий, которая привела к определенному результату, если вы начали с нарушения предварительных условий и вызвали UB.


Ответ 2

Ваш operator<() несовместим, так как key1<key2 и key2<key1 могут быть true (пример: key1={1,3,0}, key2={3,1,0}). Вы должны дать переменным-членам приоритет в сравнении:

    if (a < rhs.a) {
        return true;
    } else if (a == rhs.a) {
        if (b < rhs.b) {
            return true;
        } else if (b == rhs.b) {
            if (c < rhs.c) {
                return true;
            }
        }
    }
    return false;

Ответ 3

В качестве ключа вы действительно можете использовать стандартный класс std::tuple.

Тем не менее оператор можно определить следующим образом:

bool operator <( const Key &rhs ) const
{
    return
    ( a < rhs.a ) ||
    ( !( rhs.a < a ) && ( b < rhs.b ) ) ||
    ( !( rhs.a < a ) && !( rhs.b < b ) && ( c < rhs.c ) );
};

Чтобы этот оператор работал все, что вам нужно, это то, что для типа объектов a, b и c будет определен operator < Конечно, для арифметических типов он уже определен.

На самом деле это то же самое, что

#include <tuple>

//...

bool operator <( const Key &rhs ) const
{
    return std::tie( a, b, c ) < std::tie( rhs.a, rhs.b, rhs.c );
}