Как заставить set :: find () работать для пользовательских объектов класса? - PullRequest
2 голосов
/ 20 марта 2012

Я немного озадачен использованием STL set::find() для набора моих собственных определенных объектов класса.

Мой класс содержит более двух элементов (3/4/5 и т. Д.), Так как я могу перегрузить оператор less?

Я пробовал 3 переменные, которые работают следующим образом и работают нормально:

return( (a1.i < a2.i) ||
    (!(a1.i > a2.i) && (a1.f < a2.f)) ||
    (!(a1.i > a2.i) && !(a1.f > a2.f) && (a1.c < a2.c)));

где, a1 и a2 являются объектами класса и (i, f и c являются членами класса).

Теперь я хочу обобщить это для n членов, но мой find() не всегда работает.

Я просматривал подробную документацию STL, пытался узнать, как реализован set::find() и почему ему нужно меньше (<) перегрузок операторов.

Я ссылался на документацию по sgi и msdn, но я также не смог найти много подробностей о реализации set::find().

Что я делаю не так в моей set::find() реализации?

Ответы [ 5 ]

4 голосов
/ 20 марта 2012

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

return std::tie(lhs.i, lhs.f, lhs.c) < std::tie(rhs.i, rhs.f, rhs.c);

Для этого требуется, чтобы каждый член был сопоставимого типа, например, lhs.i < rhs.i имеет смысл.

Обратите внимание, что std::tie и std::tuple доступны только для C ++ 11, поэтому для C ++ 03 вы можете использовать, например, Boost.Tuple, который обеспечивает boost::tie (boost::tuple использует тот же порядок, что и std::tuple).

Относительно того, куда это должно пойти, принято указывать это в operator< (после всего этого в первую очередь возможно использование tie для простого заказа).,Довольно часто этот оператор будет другом, поэтому он будет выглядеть следующим образом:

class foo {
public:
    /* public interface goes here */

    // declaration of non-member friend operator
    // if it doesn't need to be a friend, this declaration isn't needed
    friend
    bool operator<(foo const& lhs, foo const& rhs);

private:
    T t;
    U u;
    V v;

};

bool operator<(foo const& lhs, foo const& rhs)
{
    // could be boost::tie
    return std::tie(lhs.t, lhs.u, lhs.v) < std::tie(rhs.t, rhs.u, rhs.v);
}

Как вы можете видеть, он не полностью автоматический, так как реализация operator< должна перечислять каждого члена foo (илипо крайней мере те, которые имеют значение для заказа), дважды.Боюсь, нет лучшего способа.

Вместо предоставления operator< вы можете специализировать std::less для foo, но это немного экзотично и не является предпочтительным способом.Если упорядочение по-прежнему не имеет смысла быть частью расширенного интерфейса foo (например, может быть более одного упорядочения, которое имеет смысл без канонического), тогда предпочтительным способом является написание функтора:

struct foo_ordering {
    bool operator()(foo const& lhs, foo const& rhs) const
    {
        /* implementation as before, but access control/friendship
           has to be planned for just like for operator< */
    }
};

Тогда вы будете использовать, например, std::set<foo, foo_ordering>.

Имейте в виду, что независимо от того, какую форму принимает заказ (через operator<, std::less<foo> или функтор), если он используетсяс std::set или любым другим ассоциативным контейнером (и по умолчанию, например, std::set<T> использует std::less<T>, который, в свою очередь, использует operator< по умолчанию), он должен следовать некоторым строгим критериям, то есть это должен быть строгий слабый порядок.Однако если все элементы, которые используются для заказа foo, имеют заказы SW, то результирующее лексикографическое упорядочение также является заказом SW.

4 голосов
/ 20 марта 2012

Вы должны определить строгий порядок ваших объектов. Поэтому, если ваш объект состоит из n членов a_1 .. a_n, которые имеют строгий порядок, то вы можете сделать следующее:

bool operator< (const TYPE &rhs) {
  if (a_1 < rhs.a_1) return true; else if (a_1 > rhs.a_1) return false;
  if (a_2 < rhs.a_2) return true; else if (a_2 > rhs.a_2) return false;
  ...
  if (a_n < rhs.a_n) return true;
  return false;
}

Edit: Если вам подойдет либо повышение, либо C ++ 11, вам действительно следует использовать метод std::tie / boost::tie, который предлагает Люк Дантон в своем ответе. Это намного чище.

2 голосов
/ 20 марта 2012

Функция сравнения элементов std :: set должна определять отношение строгого слабого порядка в области элементов. Используя это определение, мы можем сказать, что два элемента эквивалентны, если сравнение (a, b) ложно и сравнение (b, a) также ложно. std :: find может быть реализован с использованием этого предположения.
Вы можете найти больше здесь: http://www.sgi.com/tech/stl/set.html и http://www.sgi.com/tech/stl/StrictWeakOrdering.html

1 голос
/ 20 марта 2012

Это только частичный ответ, но подробную документацию по STL можно найти на веб-сайте SGI .

1 голос
/ 20 марта 2012

Ваш operator < должен быть способен сравнивать каждый объект с данным, вот так

struct Data
{
    bool operator < (const Data& right) const
    {
    return( (this.i < right.i) ||
        (!(this.i > right.i) && (this.f < right.f)) ||
        (!(this.i > right.i) && !(this.f > right.f) && (this.c < right.c)));
    }
}

Кроме того, ваш алгоритм сравнения выглядит сомнительным, поскольку он не учитывает случаи, когда

this.i == right.i

или

this.f == right.f

И вас на самом деле не должно интересовать std::set реализация. Он может меняться от компилятора к компилятору и может быть изменен в будущем. Ваша программа должна делать предположения только о контейнере интерфейс , никогда реализация .

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