size_t: оператор? (и способ использования unordered_set)

Что

operator size_t () const

Среда: Visual Studio 2010 Professional


TL; DR

Сегодня я искал способ использовать std::tr1::unordered_set. Поскольку в прошлый раз я спросил как использовать std::map, я решил выяснить это сам.

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

мне нужно реализовать

bool operator == (const edge & another) const

а также

operator size_t () const

Полученный код находится ближе к концу вопроса.

== знаком без проблем. size_t тоже знакомо. Но что такое operator size_t?

Это похоже на equals и hashCode для Java, которые должны быть переопределены вместе в соответствии с Effective Java. Но я не уверен, особенно когда имя size_t.


Полученный код выглядит следующим образом. Вся программа работает нормально и выдает правильные результаты.

class edge {
public:
    int x;
    int y;
    edge(int _x, int _y) : x(_x), y(_y) {
    }
    bool operator == (const edge & another) const {
        return (x == another.x && y == another.y);
    }
    operator size_t () const {
        return x * 31 + y;
    }
};

Немного больше:

Нет

size_t operator () const

который не может быть скомпилирован:

error C2143: syntax error : missing ';' before 'const'
error C4430: missing type specifier - int assumed. Note: C++ does not support default-int
error C2059: syntax error : '{'
error C2334: unexpected token(s) preceding '{'; skipping apparent function body

Даже нет

int operator size_t () const

но как я вижу функция возвращает int. Код ошибки выглядит следующим образом:

error C2549: user-defined conversion cannot specify a return type

person Dante May Code    schedule 05.07.2011    source источник


Ответы (5)


Это оператор приведения типа. В основном обеспечивает неявное преобразование объекта в указанный тип, в данном случае size_t.

ИЗМЕНИТЬ:

Скажем, у вас есть функция, определенная следующим образом:

void Foo( size_t x )
{
  // do something with x
}

Если ваш класс edge определяет оператор приведения типа для преобразования в size_t, вы можете сделать следующее:

edge e;
Foo( e );

Компилятор автоматически преобразует объект edge в size_t. Как говорит @litb в разделе комментариев, не делайте этого. Неявные преобразования могут вызвать проблемы, позволяя компилятору выполнять преобразования, когда вы, возможно, не предполагали, что это произойдет.

Вместо этого вы должны определить функцию-член, такую ​​​​как edge::to_size_t() (я знаю, что это ужасное имя), чтобы выполнить преобразование.

Например, std::string определяет функцию-член std::string::c_str() вместо определения оператора приведения типа для преобразования в const char *.

EDIT 2: Извините, я недостаточно внимательно прочитал ваш вопрос. Теперь я вижу, что вы пытаетесь использовать свой класс в std::unordered_set. В этом случае вы должны определить функторы, которые выполняют операции хеширования и сравнения для вашего класса. Кроме того, вы можете предоставить специализации шаблона std::hash и std::equal_to для своего класса и не указывать необязательные параметры шаблона при создании объекта unordered_set.

Как вы задали вопрос, это очень похоже на функцию-член hashCode() в Java, но поскольку не все классы С++ происходят от общего базового класса, такого как классы Java, он не реализован как функция базового класса с возможностью переопределения.

person Praetorian    schedule 05.07.2011
comment
Не могли бы вы предоставить более подробную информацию о том, как работает это неявное преобразование? И зачем его нужно конвертировать в size_t? - person Dante May Code; 05.07.2011
comment
@ Данте не гик: я добавил больше деталей. - person Praetorian; 05.07.2011
comment
Я меняю operator size_t () const на size_t to_size_t() const, но это не работает. Ваши добавленные данные означают, что вообще я не должен писать такую ​​конверсию, верно? В std::tr1::unordered_set кажется, что это требуется, и вот почему я спросил, почему. Он работает как хеширование, поскольку он преобразуется в целое число без знака (то есть size_t)? - person Dante May Code; 05.07.2011
comment
@ Данте не компьютерщик: да, вы абсолютно правы; Я снова отредактировал ответ :-). В общем, идея плохая, но необходимая в данном случае. - person Praetorian; 05.07.2011
comment
Извините, но последнее (может быть...): нормальный способ включить хэш-функцию - определить ее вне самого класса? - person Dante May Code; 05.07.2011
comment
@Dante не гик: да, вы можете определить его как функторы вне самого класса. Вот пример: stackoverflow.com/questions/2099540/. Другой способ сделать это — предоставить специализации шаблонов для std::hash() и std::equal_to для вашего типа класса. - person Praetorian; 05.07.2011
comment
@Praetorian: Вы уверены, что преобразование-в-размер_t автоматически используется std::hash<T>? Я только что попробовал это, и это не сработало. Насколько я знаю, вы должны специализировать std::hash<T> вручную, никак иначе - нет, я член хеш-функции. - person Kerrek SB; 05.07.2011
comment
@Kerrek SB: Я могу ошибаться, но что, если вы укажете оператор приведения типа, а затем передадите свой класс в качестве необязательного параметра шаблона хеш-функции в unordered_map? - person Praetorian; 05.07.2011
comment
@Pretorian: Как это должно работать, unordered_set<Foo, Foo>? Хэш-класс должен быть вызываемым типом, а оператор приведения не обеспечивает такое поведение. - person Kerrek SB; 05.07.2011
comment
@Kerrek SB: Вы правы, перегрузка оператора приведения типа не работает. Но вы можете определить оператор вызова функции, который принимает константную ссылку на ваш объект, а затем использует тип вашего класса для хеш-функции. Сделайте то же самое для операции сравнения, и вы можете использовать unordered_set< Foo, Foo, Foo > - person Praetorian; 05.07.2011
comment
@Pratorian: Хорошо, вы могли сделать это, но вы бы получили значок за самый нелогичный дизайн! (А что, если эти операторы уже заняты?) Основная проблема заключается в том, что вам нужно помнить об этом, когда вы пишете параметры своего контейнера, поэтому я обычно предпочитаю специализацию std::hash. Почти всегда даже нет необходимости иметь более одной хеш-функции для типа... - person Kerrek SB; 05.07.2011
comment
@Kerrek SB: Я согласен, это было бы нелогичным дизайном (я даже сам проголосовал за ваш комментарий :-)). Во всяком случае, я отредактировал ответ. - person Praetorian; 05.07.2011

Это оператор преобразования, как намекает error C2549: user-defined conversion cannot specify a return type. Он определяет, как ваш тип может быть преобразован в size_t в этом случае. В общем, operator X() {...} указывает, как создать X из вашего типа.

person carlpett    schedule 05.07.2011
comment
Не могли бы вы предоставить более подробную информацию о том, как работает это преобразование? И зачем его нужно конвертировать в size_t? - person Dante May Code; 05.07.2011

Что такое operator size_t () const?

Это функция преобразования. Это функция, которая позволяет вам неявно преобразовывать объект вашего класса в тип size_t. См. дополнительную информацию и примеры в ссылке, которую я предоставил. Хт.

person Armen Tsirunyan    schedule 05.07.2011
comment
Не могли бы вы предоставить более подробную информацию о том, как работает это неявное преобразование? И зачем его нужно конвертировать в size_t? - person Dante May Code; 05.07.2011
comment
@Dante не грек: я считаю, что ссылка, которую я предоставил, содержит несколько примеров и хорошее объяснение. Нет? - person Armen Tsirunyan; 05.07.2011
comment
ссылка не отвечает на вторую часть вопроса. - person Dante May Code; 05.07.2011
comment
@Dante: У нас недостаточно информации, чтобы сказать наверняка, но похоже, что сомнительно звучащий подход к использованию unordered_set, который вы искали в Google, требует, чтобы объекты использовали этот оператор преобразования для предоставления своего хэш-кода. Это довольно опасно, поскольку не позволяет компилятору обнаруживать такие ошибки, как ввод int x = edge; вместо int x = edge.x;. С оператором преобразования это присвоит хешу ребра значение x. Как вы говорите в вопросе, обычный подход состоит в том, чтобы определить функтор для вычисления хеша. - person Mike Seymour; 05.07.2011
comment
@Mike Seymour, а функтор должен быть определен где-то вне класса? - person Dante May Code; 05.07.2011
comment
@Dante: должна быть просто функция-член с именем GetHash() - person Armen Tsirunyan; 05.07.2011
comment
@Dante: я бы определил его вне класса, но вы можете вложить его внутрь, если хотите. Затем вам нужно указать его при объявлении вашего типа набора: unordered_set<edge, edge_hash>. В качестве альтернативы вы могли бы определить его как специализацию std::tr1::hash, тогда вам не нужно будет указывать его в качестве аргумента для unordered_set. - person Mike Seymour; 05.07.2011

В любом классе Foo operator T () const является оператором приведения, который позволяет преобразовать Foo в T:

Foo x;
T y = x; // invokes Foo::operator T() const

Например, std::fstream имеет оператор приведения к логическому значению, поэтому его можно использовать в таких выражениях, как if (mystream) ....


В ответ на вашу потребность в использовании неупорядоченных контейнеров: вам нужно будет реализовать хеш-функцию или объект функции, который соответствует сигнатуре size_t (const Foo &). Если вы хотите сделать это с наименьшим воздействием на код пользователя, выберите std::hash<Foo> специализацию:

size_t my_magic_hash(const Foo &); // defined somehow
namespace std {
  template <>
  struct hash<Foo> : public std::unary_function<const Foo &, std::size_t>
  {
    inline std::size_t operator()(const Foo & x) const
    {
      return my_magic_hash(x);
    }
  };
}

Теперь мы можем использовать std::unordered_set<Foo> напрямую, при условии, что Foo предоставляет operator==.

person Kerrek SB    schedule 05.07.2011
comment
Обратите внимание, что приведенное выше также работает, если определено T::T(const Foo&). - person Node; 05.07.2011
comment
@Node: Да, либо вы конвертируете A в B, либо создаете B из A. Что произойдет, если оба определены, однако имеет ли приоритет конструктор с одним аргументом? - person Kerrek SB; 05.07.2011
comment
Почему его нужно преобразовать в size_t? - person Dante May Code; 05.07.2011
comment
@ Данте: Что ты имеешь в виду? Я отвечаю на два вопроса: 1) что такое оператор приведения и 2) как указать хеш-функцию. Это разные вещи. Хеш-функция — это обычная функция (или функтор), там используются не операторы приведения. - person Kerrek SB; 05.07.2011
comment
@Kerrek SB, я знаю, моя почему заключается в том, что size_t действует как хеширование. - person Dante May Code; 05.07.2011
comment
@Dante: тип на самом деле вообще не действует - это просто удобный, универсальный, целочисленный тип без знака машинно-удобного размера! Какой лучший тип был бы для хеш-значения? Вы хотите, чтобы он был быстрым и большим, так что это именно тот тип, который вам нужен. - person Kerrek SB; 05.07.2011
comment
@Kerrek - я только что попробовал это на IdeOne, и похоже, что оператор преобразования имеет приоритет. - person Node; 05.07.2011
comment
@Node: Мило! Как ни странно, похоже, это зависит от константности оператора приведения: неконстантно, const. Также обратите внимание, что между B b(a) и B b = a есть разница! - person Kerrek SB; 05.07.2011

Это неявный оператор преобразования. В основном это позволяет использовать объект вашего класса в контексте, где ожидается size_t (вызов этого оператора для выполнения преобразования).

Чтобы использовать unordered_set, вам нужна какая-то хеш-функция. В данном случае он маскируется под operator size_t, что я не очень рекомендую, потому что это просто скрывает тот факт, что это хэш-функция. Я бы просто пошел дальше и определил настоящую хеш-функцию/функтор и использовал ее вместо этого. Это будет более ясно, и будущие сопровождающие будут вам благодарны.

person Mark B    schedule 05.07.2011
comment
Я считаю (ред) лучшим способом, поскольку теперь все вещи могут быть включены в сам класс. Может ли настоящая хеш-функция/функтор быть включена в сам класс? Не могли бы вы дать более подробную информацию о реальном хешировании? - person Dante May Code; 05.07.2011