Есть ли способ сохранить ссылки/указатели на объекты в unordered_set?

У меня есть пара объектов class A. В некоторых других class B я хочу сохранить unordered_set объектов class A. Обратите внимание, что объекты class B, таким образом, содержат набор, который может указывать на (некоторые) те же самые объекты class A, что и другой набор. Требуется, чтобы эти объекты class A никогда не копировались или что-либо еще, поскольку они содержат данные членов, которые должны использоваться и изменяться (совместно используемые) различными объектами class B и class A.

Во-первых, поскольку я пришел из C, я думал просто использовать указатели на объект в качестве значений в наборе. Однако при написании кода я увидел, что теперь мне нужно также передать функции переопределенного хеширования и равенства, поскольку я должен сравнивать объекты класса A на предмет равенства в частном члене class A. Таким образом, функция хеширования должна быть общедоступной функцией-членом class A.

Однако мне было интересно, поскольку std::unordered_set::insert(), похоже, берет ссылку на value_type, можно ли просто использовать A в качестве типа значения для std::unordered_set. Это сделало бы код немного проще, поскольку по умолчанию он будет использовать == operator, уже реализованный в class A. Однако я не уверен, копирует ли std::unordered_set::insert() объекты, которые вы ему передаете. Вроде нет, но value_type остается A, а не A&.

Я также не вижу разницы между std::unordered_set::insert() и std::unordered_set::emplace(). Я не понимаю, что означает «копировать против перемещения».

TLDR: чтобы сохранить ссылки на объекты определяемых пользователем классов в наборе или карте, возможно ли в этом случае использовать A* как value_type? Или std::reference_wrapper<A>? Или просто A?

Вот минимальный воспроизводимый пример:

class A {
public:
    bool operator== (A& other);
private:
    B* b;
    size_t id;
    size_t someIntThatMustBeShared;
};

class B {
public:
    std::unordered_set<A ? > as;
};

person Adriaan Jacobs    schedule 28.03.2020    source источник
comment
И указатели, и ссылки имеют свое применение в C++. Вы должны понимать, для чего каждый из них должен использоваться, а когда его не следует использовать. В С++ нет универсального ответа для указателей или ссылок. На самом деле, каждый вопрос, что лучше делать X или Y в C++, имеет один и тот же ответ: да.   -  person Sam Varshavchik    schedule 28.03.2020
comment
@SamVarshavchik Кажется, я недостаточно ясно изложил свой вопрос. Я считаю, что достаточно осведомлен о разнице между указателями и ссылками, и я также считаю, что мой вопрос показывает это. Любые отзывы о том, как улучшить ясность вопроса, конечно, высоко ценятся в этом отношении. Я хочу специально спросить о том, как можно было бы использовать указатели, ссылки или объекты в качестве типов значений и какой из них будет лучшим вариантом для варианта использования, описанного в вопросе.   -  person Adriaan Jacobs    schedule 28.03.2020
comment
Вы можете знать о разнице между ними, но вы задаете этот вопрос, потому что не знаете, когда лучше всего использовать тот или иной, и это то, что я специально упомянул в своем комментарии. Кроме того, если вы попытаетесь использовать ссылки в unordered_set, у вас ... вряд ли получится. В какой-то момент, рано или поздно, вы поймете, что вы не можете сделать это в C++, и самое большее, что вы можете сделать, это использовать std::reference_wrapper. Итак, прежде чем вы начнете размышлять о том, какой вариант лучше, похоже, вам нужно еще немного поучиться, вот.   -  person Sam Varshavchik    schedule 28.03.2020
comment
@SamVarshavchik Спасибо. Я наткнулся на проблему, которую вы описали в своем комментарии, и попытался реализовать решение с помощью std::reference_wrapper. Вы хорошо заметили, что это неясно из исходного вопроса, я отредактирую его. При этом я хотел бы подчеркнуть, что я не ищу какого-либо общего наилучшего решения, но я считаю, что предоставил достаточно конкретный пример использования, чтобы запросить конкретное решение в all, так как я не могу найти тот, который работает в любом случае. Вы советуете мне продолжать попытки с reference_wrapper? Это предпочтительный шаблон в данном случае?   -  person Adriaan Jacobs    schedule 28.03.2020
comment
Может быть. Разумное решение о том, следует ли использовать указатели, ссылки или дискретные объекты, не может быть принято исключительно на основе предоставленной информации. Это зависит от многих, многих других факторов. C++ — самый сложный из используемых сегодня языков программирования общего назначения. Например, основываясь исключительно на показанном коде, ответом будет не использовать указатели или ссылки, а просто хранить их в наборе. Может быть много причин, по которым ответ будет другим, как только вы добавите еще одну строку в показанный код. Вот почему никто на stackoverflow.com не может дать вам ответ.   -  person Sam Varshavchik    schedule 28.03.2020
comment
From cppreference Он часто используется как механизм для хранения ссылки внутри стандартных контейнеров. Мне кажется, это хороший вариант.   -  person super    schedule 28.03.2020
comment
@SamVarshavchik Не приводит ли хранение объектов непосредственно в наборе к копированию A::someIntThatMustBeShared при вставке через std::unordered_set::insert()? Я предполагаю, что изменение этого целого числа в скопированном объекте не будет отражено во всех других копиях объекта.   -  person Adriaan Jacobs    schedule 28.03.2020
comment
@super Да, у меня есть версия программы с этим реализованным. Однако я обнаружил, что мне все равно пришлось реализовать как функтор хэширования, так и функтор равенства для std::unordered_set, поскольку std::reference_wrapper<T>, похоже, не вызывает operator == на своем <T>. Итак, я писал тот же код, что и при использовании указателей. Тогда я спросил себя, в чем смысл, и не упускаю ли я из виду какое-то простое решение этой (думаю, не такой уж редкой) проблемы, используя, например, фактический объект в качестве ключа и динамически выделяя все члены.   -  person Adriaan Jacobs    schedule 29.03.2020
comment
Необходимо поделиться — слишком широкое описание, чтобы быть полезным. Общее может означать что угодно. Ваше предположение верно. Тем не менее, есть способы справиться с этим, например std::shared_ptr, который позволяет избежать прямого использования указателей. Вот почему я написал: C++ — самый сложный из используемых сегодня языков программирования общего назначения. Каким бы ни был лучший подход к показанному коду, это совершенно, совершенно и совершенно неуместно и бессмысленно. Потому что, как только вы измените одну строку показанного кода, лучший подход, скорее всего, будет другим. Вы должны решить это на основе вашего всего кода.   -  person Sam Varshavchik    schedule 29.03.2020
comment
@AdriaanJacobs Тот факт, что я до сих пор не знаю, проверяет ли этот компаратор на равенство, сравнивая, ссылаются ли они на один и тот же объект, или если объекты имеют одинаковое значение, говорит мне, что, вероятно, имеет смысл не иметь поведения по умолчанию.   -  person super    schedule 29.03.2020
comment
@SamVarshavchik Я стараюсь изо всех сил, но я действительно не знаю, как это лучше описать. Когда я написал они содержат данные элементов, которые должны использоваться и изменяться (совместно используемые) различными объектами, я имел в виду, что class A имеет целое число, которое должно обновляться несколько раз, из множество источников (под этим я подразумеваю, что целое число может быть доступно через ссылку на объект, который содержится в наборе где угодно). Поскольку вы подтвердили, что std::unordered_set::insert()копирует аргумент, как std::unordered_set::emplace() ведет себя по-другому? Предлагает ли это решение?   -  person Adriaan Jacobs    schedule 29.03.2020
comment
@super Прямо сейчас я реализую это, сохраняя необработанные указатели в неупорядоченном наборе, а затем определяя хэш и эквивалентный функтор как внутренние классы A (внутренние, потому что мне нужен доступ к закрытым членам для их реализации). Однако я борюсь с проблемой кругового включения, и я не могу объявить внутренние классы. Я действительно думаю, что для этого должен быть шаблон проектирования, но я не могу правильно выполнить поиск в Google.   -  person Adriaan Jacobs    schedule 29.03.2020
comment
Это вполне понятно, но вы ничего не узнаете, спрашивая у незнакомцев их мнения о том, что лучше, X или Y. Вам нужно узнайте что означают X и Y и чем они отличаются, чтобы вы могли самостоятельно решить, какое решение лучше для вас, на основе того, что вы узнали< /б>. Я не буду вдаваться в учебник о том, что такое emplace, что это значит и как это работает. Полное объяснение см. в вашей книге по C++. К сожалению, stackoverflow.com не является учебным сайтом по C++ и не заменяет хорошую книгу по C++.   -  person Sam Varshavchik    schedule 29.03.2020
comment
@SamVarshavchik Я пытался использовать std::shared_ptr, но я, должно быть, неправильно понял, как они работают, потому что казалось, что мне все еще нужно было предоставить свой собственный хэшер и эквалайзер, чтобы использовать их в std::unordered_set.   -  person Adriaan Jacobs    schedule 29.03.2020
comment
@SamVarshavchik Хорошо, сэр, извините, если побеспокоил вас. Я только начал изучать C++ на этой неделе, и у меня нет никакого учебного материала, так как я не изучаю его как класс, а скорее изучаю его как часть дипломного проекта, в котором я участвую. Это первый раз, когда я я написал на stackoverflow.com за помощью, и я все еще немного озадачен, что можно спрашивать, а что нет.   -  person Adriaan Jacobs    schedule 29.03.2020
comment
Правильный. В С++ очень мало того, что происходит автоматически от вашего имени. Вы должны сделать всю работу самостоятельно. Сколько раз я повторял, что C++ — самый сложный из используемых сегодня языков программирования общего назначения? Вы начинаете понимать, почему. Для достижения среднего или профессионального уровня квалификации требуется 3-5 лет.   -  person Sam Varshavchik    schedule 29.03.2020
comment
@SamVarshavchik Итак, поскольку мне все еще нужно реализовать эти функторы самостоятельно, почему бы мне просто не использовать необработанный указатель в качестве value_type, а затем также реализовать их самостоятельно? Или std::reference_wrapper? Я хочу отметить здесь, что я уже сказал это в своем первом ответе на super.   -  person Adriaan Jacobs    schedule 29.03.2020
comment
Существует большая разница между использованием std::shared_ptr и обычного указателя.   -  person Sam Varshavchik    schedule 29.03.2020


Ответы (1)


ОТКАЗ ОТ ОТВЕТСТВЕННОСТИ: я оператор и новичок в C++. Мой код на данный момент компилируется и работает, как и ожидалось, и хотя я считаю, что вся приведенная ниже информация верна, пожалуйста, оставьте комментарий, чтобы уведомить меня о любых ошибках.

Для тех, кто заинтересован, я закончил тем, что создал два общедоступных функтора в классе A (внутренний, так как мне нужен был доступ к закрытым членам (использование friend также было бы вариантом)) и использовал их в объявлении std::unordered_set. Для типа я использовал A *. Лучшим решением мог бы быть умный указатель, но я новичок во всем этом, поэтому я еще не изучал их подробно.

Я узнал следующее о std::unordered_set:

  1. Поведение приравнивания и хеширования по умолчанию реализовано только для типов, перечисленных здесь.
  2. Если вам нужно что-то еще, у вас есть 3 варианта: использовать какой-то указатель на тип (будь то сырой, std::shared_ptr, std::unique_ptr,...), использовать std::reference_wrapper<T> или использовать сам тип. Обратите внимание, что такие ссылки, как A&, не разрешены в качестве value_type для std::unordered_set, следовательно, и для оболочки.
  3. В первых двух вариантах реализация по умолчанию (если есть) не «каскадирует вниз» для использования любого operator ==, который вы, возможно, определили для своего типа. В пункте 5 предлагается решение этой проблемы.
  4. В последнем варианте operator == вашего типа используется как «эквивалент» по умолчанию. Для работы здесь требуется только определение хэшера. Однако вы должны быть осторожны, так как std::unordered_set::insert() создает копию вашего вставленного элемента, поэтому, если он содержит статически размещенные данные членов, вы должны убедиться, что ваша программа по-прежнему ведет себя правильно, когда вы начинаете копировать экземпляры типа. Я думаю, что это наименее подробное решение, и это также решение, для которого я нашел больше всего примеров. (Использование std::unordered_set::emplace() может быть решением проблемы копирования, но я еще не совсем понял это.)
  5. В общем, функциональность «хэширования» и «эквалайзера» может быть реализована двумя способами: через определение std::hash<T>и/или std::equal_to<T> или путем создания функторов для этих целей. Подробнее о том, как это сделать здесь< /а>. По сути, они делают одно и то же, поэтому их можно менять местами. Как обсуждалось в пункте 4, есть еще один вариант, когда вы сохраняете прямые типы, вы также можете использовать operator ==, и тогда вам не нужно указывать какой-либо «равный» при объявлении std::unordered_set.
person Adriaan Jacobs    schedule 28.03.2020