Суть простая: приложение поключается к набору серверов, на каждом сервере есть набор клиентов. Для каждого клиента есть объект, через который приложение с ним работает. Хочу сохранить это все в map<int,map<int,unique_ptr<X>>. Map будет владеть объкетами, при удалении map все объекты долны быть освобождены.
При работе с таким мапом получаю шибку компиляции, которую не могу распарсить.
Здравствуйте, Qbit86, Вы писали:
Q>Здравствуйте, gandjustas, Вы писали:
G>>При работе с таким мапом получаю шибку компиляции, которую не могу распарсить.
Q>Отсутствует конструктор копирования? Q>
error: use of deleted function 'constexpr std::pair<_T1, _T2>::pair(const std::pair<_T1, _T2>&) [with _T1 = const int; _T2 = std::unique_ptr<X>]
Q>Попробуй заменить на shared_ptr<T>.
C shared все ок. Меня интересует что не так с unique_ptr. В теории ничего криминального не делаю, unique_ptr должен работать в std контейнерах, в интернете есть даже примеры.
С shared_ptr наоборот натыкаюсь на рекомендации что лучше так не делать.
Здравствуйте, Sharpeye, Вы писали:
S>Здравствуйте, gandjustas, Вы писали:
G>>При работе с таким мапом получаю шибку компиляции, которую не могу распарсить.
S>Попробуй заменить S>
S>m[a] = {};
S>
S>на S>
S>m[a].clear();
S>
Первый иф можно вообще убрать, без него прекрасно работает.
Все никак не могу привыкнуть к тому что объекты это не ссылки.
Здравствуйте, gandjustas, Вы писали:
G>Господа, подскажите что не так с этим кодом: http://cpp.sh/2rxid
G>Суть простая: приложение поключается к набору серверов, на каждом сервере есть набор клиентов. Для каждого клиента есть объект, через который приложение с ним работает. Хочу сохранить это все в map<int,map<int,unique_ptr<X>>. Map будет владеть объкетами, при удалении map все объекты долны быть освобождены.
G>При работе с таким мапом получаю шибку компиляции, которую не могу распарсить.
У типа вложенного map (map<int,unique_ptr<X>>) удален оператор присваивания, т.к. элементы не могут быть присвоены из-за удалённоего оператора присваивания в unique_ptr<X>
В строке 15 m[a] возвращает ссылку на такой неприсваевыемый map, т.е. это
map<int,unique_ptr<X>> default_constructed; // OK
map<int,unique_ptr<X>> & mm = a[m]; // OK
mm = default_constructed; // нельзя, т.к. unique_ptr<X>::operator=() = delete;
В C++ map/unordered_map при доступе по ключу происходит find и вставляется значение по умолчанию, если ключ не найден. Т.е. find() лишний в этом случае. Часто вижу такой код у практикующих Java, там у Map нет метода в стиле insert_or_assign? То же самое, но оптимальнее:
void f(std::map<int, std::map<int,std::unique_ptr<X>>>& m, int a, int b, X x)
{
auto &mm = m[a];
mm[b] = std::make_unique<X>(x);
// или даже так
m[a][b] = std::make_unique<X>(x);
}
Дальше пойдут непрошенные советы, но мы ведь на русском форуме, а не на StackOverflow
С точки зрения компактности хранения и локальности доступа к памяти, map<int,map<int,unique_ptr<X>> это, скорее всего, будет тройной косвенный доступ к экземплярам X.
Вам действительно нужено упорядоченное по ключу перечисление элементов map? Если нет, то возможно стоит для начала рассмотреть использование другого стандартного ассоциативного контейнера unordered_map.
Вы действительно должны динамически создавать и хранить экземпляры X или может быть достаточно храненить их по значению?
Если X — это базовый класс в известной и фиксированной иерархии, а хранить нужно его потомков, то можно рассмотреть использование std::variant<Derived1_from_X, Derived2_from_X, ..> для небольших N.
Вам точно нужнен map of map? Если множество ключей уникально, то составной ключ может быть структурой с определенным operator< для map, или operator== и std::hash для unordered_map:
class X {
};
struct Key
{
int a;
int b;
bool operator==(Key const& key) const
{
return a == key.a && b == key.b;
}
struct hash
{
size_t operator()(Key const& key) const
{
std::hash<int> const hasher;
return hasher(key.a) ^ hasher(key.b);
}
};
};
using Map = std::unordered_map<Key, X, Key::hash>;
void f(Map& m, int a, int b, X x)
{
m[Key{a, b}] = std::move(x);
}
Еще нередко бывает, что ключ уже является частью объекта X. Тогда вполне достаточно хранения объектов в set, слава heterogeneous lookup in associative containers. К сожалению, для unordered_set это доступно в стандартной библиотеке только в C++20
Здравствуйте, PM, Вы писали:
PM>В C++ map/unordered_map при доступе по ключу происходит find и вставляется значение по умолчанию, если ключ не найден. Т.е. find() лишний в этом случае. Часто вижу такой код у практикующих Java, там у Map нет метода в стиле insert_or_assign?
Конечно нет. В других языках вообще не принято неявно вызвать конструкторы. И все контейнеры — ссылочные типы, поэтому дефолт для них — null.
PM>То же самое, но оптимальнее: PM>
PM>void f(std::map<int, std::map<int,std::unique_ptr<X>>>& m, int a, int b, X x)
PM>{
PM> auto &mm = m[a];
PM> mm[b] = std::make_unique<X>(x);
PM>// или даже так
PM> m[a][b] = std::make_unique<X>(x);
PM>}
PM>
Очень неочевидный код для тех кто работает на других языках.
PM>Дальше пойдут непрошенные советы, но мы ведь на русском форуме, а не на StackOverflow
Собственно ради них сюда и пришел.
PM>С точки зрения компактности хранения и локальности доступа к памяти, map<int,map<int,unique_ptr<X>> это, скорее всего, будет тройной косвенный доступ к экземплярам X. PM>Вам действительно нужено упорядоченное по ключу перечисление элементов map? Если нет, то возможно стоит для начала рассмотреть использование другого стандартного ассоциативного контейнера unordered_map.
Я, как человек, который не знает C++ выбрал тип, который короче пишется.
unordered_map в данном случае чем лучше обычного map?
PM>Вы действительно должны динамически создавать и хранить экземпляры X или может быть достаточно храненить их по значению?
X это обертка над указателем, возвращаемым функцией COM-объекта, у X есть нетривиальные методы. X владеет этим указателем. Дефолтного конструктора у X нет.
Чтобы X по значению можно было передавать в контейнер надо реализовать конструкторы перемещения (или копировани, я хз), а это для меня, как человека, который не знает C++, оказалось слишком сложно.
Поэтому я и взял unqiue_ptr, потому что я могу ему по факту любой класс передать и получить поведение, которое ожидаю. По крайней мере я так думал пока не попытался сделать map.
PM>Вам точно нужнен map of map? Если множество ключей уникально, то составной ключ может быть структурой с определенным operator< для map, или operator== и std::hash для unordered_map:
map of map выбрал потому что при отключении от сервера можно будет сделать m[x]. clear(). Если
Здравствуйте, Qbit86, Вы писали:
Q>Здравствуйте, gandjustas, Вы писали:
G>>Все никак не могу привыкнуть к тому что объекты это не ссылки.
Q>А с чего вообще взялся за C++?
В свободное время пилю плагин для игрушки, заодно новшества языка изучаю.
Современный C++ очень выразительный язык, но количество неочевидных проблем зашкаливает. Мне кажется проще чинить утечки памяти в голом C, чем заставить компилироваться нетривиальный код на C++.
Здравствуйте, gandjustas, Вы писали:
PM>>В C++ map/unordered_map при доступе по ключу происходит find и вставляется значение по умолчанию, если ключ не найден. Т.е. find() лишний в этом случае. Часто вижу такой код у практикующих Java, там у Map нет метода в стиле insert_or_assign? G>Конечно нет. В других языках вообще не принято неявно вызвать конструкторы. И все контейнеры — ссылочные типы, поэтому дефолт для них — null.
Да, для успешного понимания языка нужно принять тот факт, что в С++ многое основано на значениях, а не на указателях (хранение полей в структурах, передача параметров в/возврат из функции, выбрасывание исключений, RAII, и все контейнеры стандаратной бибилотеки)
PM>>
PM>>void f(std::map<int, std::map<int,std::unique_ptr<X>>>& m, int a, int b, X x)
PM>>{
PM>> m[a][b] = std::make_unique<X>(x);
PM>>}
PM>>
G>Очень неочевидный код для тех кто работает на других языках.
Но с двумерным массивом код ведь обычно очевиден? map — по сути тоже массив, только может быть с ключами, отличными от целых
void f(std::array<std::array<X, 10>, 10>& matrix, int a, int b, X x)
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ читать примерно как X matrix[10][10]
{
m[a][b] = x;
}
PM>>С точки зрения компактности хранения и локальности доступа к памяти, map<int,map<int,unique_ptr<X>> это, скорее всего, будет тройной косвенный доступ к экземплярам X. PM>>Вам действительно нужено упорядоченное по ключу перечисление элементов map? Если нет, то возможно стоит для начала рассмотреть использование другого стандартного ассоциативного контейнера unordered_map. G>Я, как человек, который не знает C++ выбрал тип, который короче пишется. G>unordered_map в данном случае чем лучше обычного map?
std::map — это binary search tree (реализовано обычно как red-black tree), каждый узел создается динамически, итераторы (указатели на узлы дерева) остаются действительными после вставки/удаления элементов. Следствие реализации — ключи упорядочены, легко искать их диапазоны (см. lower_bound, upper_bound, equal_range)
std::unordered_map — из-за требований стандарта к интерфейсу может быть реализован только как closed addressing hash table. При вставке/удалении элементов итераторы могут стать недействительными (указывать на мусор) из-за re-hashing. Порядок ключей не гарантирован.
Есть сторонние реализации ассоцитивных контенйров, например с отрытой адресацией, или для разреженных ключей. Выбор структуры данных за вами Обычно начинают с map или unordered_map (и в 99% случаях на этом и останавливаются)
PM>>Вы действительно должны динамически создавать и хранить экземпляры X или может быть достаточно храненить их по значению? G>X это обертка над указателем, возвращаемым функцией COM-объекта, у X есть нетривиальные методы. X владеет этим указателем. Дефолтного конструктора у X нет. G>Чтобы X по значению можно было передавать в контейнер надо реализовать конструкторы перемещения (или копировани, я хз), а это для меня, как человека, который не знает C++, оказалось слишком сложно.
G>Поэтому я и взял unqiue_ptr, потому что я могу ему по факту любой класс передать и получить поведение, которое ожидаю. По крайней мере я так думал пока не попытался сделать map.
Т.е. сейчас у вас unique_ptr<X> это фактически указатель на указатель типа такого?
X_impl* get_some_X(IComObject* obj);
class X
{
X_impl* _impl;
public:
explicit X(IComObject* from_obj)
: _impl(get_some_X(from_obj)
{
}
void do() { some comlex use of _impl; }
};
Тогда пусть X владеет этим указателем:
class X
{
std::unique_ptr<X_impl> _impl;
public:
explicit X(IComObject* from_obj)
: _impl(get_some_X(from_obj)
{
}
// если объявлен пользовательский конструктор, то версия по умолчанию сгегерирована не будет
// но мы можем явно указать компилятору, чтобы он это сделал
X() = default;
// копирование будет запрещено из-за некопируемости unique_ptr _impl,
// но перемещение пусть тоже генерирует компилятор
X(X&&) = default;
X& operator=(X&&) = default;
};
PM>>Вам точно нужнен map of map? Если множество ключей уникально, то составной ключ может быть структурой с определенным operator< для map, или operator== и std::hash для unordered_map: G>map of map выбрал потому что при отключении от сервера можно будет сделать m[x]. clear(). Если
С помощью set::equal_range(Key{x,?}) наверно можно найти все подходящие элементы. Звучит как тестовое задание, надо будет подумать про это
Но в целом да, map of map выглядит понятнее на начальном этапе.
безотносительно ошибки компиляции, можно рассмотреть вообще использование unordered_map(pair<int,int>, unique_ptr<t>), т.е. сделать ключом пару id сервера + id клиента.
PM>class X
PM>{
PM> std::unique_ptr<X_impl> _impl;
PM>public:
PM> explicit X(IComObject* from_obj)
PM> : _impl(get_some_X(from_obj)
PM> {
PM> }
PM> // если объявлен пользовательский конструктор, то версия по умолчанию сгегерирована не будет
PM> // но мы можем явно указать компилятору, чтобы он это сделал
PM> X() = default;
PM> // копирование будет запрещено из-за некопируемости unique_ptr _impl,
PM> // но перемещение пусть тоже генерирует компилятор
PM> X(X&&) = default;
PM> X& operator=(X&&) = default;
PM>};
PM>
Я так понимаю что конструктор без параметров обязателен чтобы заработал map<K1, map<K2, X>> ?
Я не хочу его добавлять чтобы у класса не было "нулевого" состояния.
Здравствуйте, gandjustas, Вы писали:
G>Я так понимаю что конструктор без параметров обязателен чтобы заработал map<K1, map<K2, X>> ?
Конструктор по-умолчанию в X нужен для map чтобы тот мог вставить значение при обращении к отсутствующему ключу. Вызов find() и
затем insert() если элемент не найден тоже вполне нормальный способ. Это я наверно запутал вас в своем начальном сообщении.
G>Я не хочу его добавлять чтобы у класса не было "нулевого" состояния.
Без проблем, если вам это нужно, при условии что X можно переместить или сконструировать при вставке:
#include <iostream>
#include <map>
#include <memory>
#include <string>
#include <cassert>
class X
{
std::unique_ptr<std::string> impl_;
public:
explicit X(std::string const& payload)
: impl_(std::make_unique<std::string>(payload))
{
}
// перемещение все-таки понадобится
X(X&&) = default;
X& operator=(X&&) = default;
std::string const& payload() const { return *impl_; }
};
int main()
{
std::map<int, std::map<int, X>> m;
// В С++17 есть такой аналог operator[]bool inserted = m[1].insert_or_assign(2, X("data")).second;
assert(inserted);
inserted = m[1].insert_or_assign(2, X("data2")).second;
assert(!inserted);
X const& assigned_x2 = m.at(1).at(2);
assert(assigned_x2.payload() == "data2");
auto &mm = m[2];
inserted = mm.emplace(4, X("data3")).second;
assert(inserted);
inserted = mm.emplace(4, X("data4")).second;
assert(!inserted);
X const& inserted_x4 = mm.at(4);
assert(inserted_x4.payload() == "data3");
std::cout << m.at(1).at(2).payload() << '\n';
auto it = m.find(2);
auto iit = it->second.find(4);
std::cout << iit->second.payload() << '\n';
it = m.find(99);
assert(it == m.end());
}
Здравствуйте, PM, Вы писали:
PM>Здравствуйте, gandjustas, Вы писали:
G>>Я не хочу его добавлять чтобы у класса не было "нулевого" состояния.
PM>Без проблем, если вам это нужно, при условии что X можно переместить или сконструировать при вставке: PM>
PM>int main()
PM>{
PM> std::map<int, std::map<int, X>> m;
PM> // В С++17 есть такой аналог operator[]
PM> bool inserted = m[1].insert_or_assign(2, X("data")).second;
PM> X const& assigned_x2 = m.at(1).at(2);
PM> assert(assigned_x2.payload() == "data2");
PM> auto &mm = m[2];
PM> inserted = mm.emplace(4, X("data3")).second;
PM> X const& inserted_x4 = mm.at(4);
PM>}
PM>
1) В чем разница между insert_or_assign и emplace?
2) Правильно понимаю что at кинет исключение если элемент не найден?
3) Как можно сделать так, чтобы конструктор не вызывался, если элемент уже есть в мапе, только через find\if?
Здравствуйте, gandjustas, Вы писали:
G>1) В чем разница между insert_or_assign и emplace?
emplace и insert не обновляют значение элемента, если такой ключ уже есть в map
insert_or_assign обновит, если ключ есть, или вставит новое значение, если ключа нет.
При вызове insert создается элемент с ключом и значением, а emplace сначала пытается найти элемент по ключу и только затем создает его (с возможной дополнительной оптимизацией — значение конструируется по месту в узле дерева, без копирования/перемещения)
В предыдущем сообщении я попытался выразить это в коде.
auto it = map.insert({ 1, X{} }); // создается pair{1, X} и перемещается в mapauto it2 = map.insert({ 1, X{} }); // создается pair{1, X} но такой ключ уже есть, только что созданный pair{1, X} уничтожается auto it = map.emplace(1, X_args); // find(1) не находит элемент, создается узел в дереве, значение X конструируется из аргументов в созданном узле дереваauto it2 = map.emplace(1, X_args); // find(1) находит элемент, возвращает итератор на него
G>2) Правильно понимаю что at кинет исключение если элемент не найден?
Да, именно так, я использовал чтобы не проверять ошибки. Это аналог такого кода:
map:::mapped_type map::at(key_type const& key)
{
auto it = find(key);
if (it == end()) throw std::out_of_range;
return it->second;
}
G>3) Как можно сделать так, чтобы конструктор не вызывался, если элемент уже есть в мапе, только через find\if?
Да, или try_emplace() из С++17
auto it = map.find(key);
if (it == map.end())
{
it = map.insert(pair(key, X(ctor_arg1, .. ctor_argN)));
}
// или
it = map.try_emplace(key, ctor_arg1, .. ctor_argN);
Здравствуйте, gandjustas, Вы писали:
G>Господа, подскажите что не так с этим кодом: http://cpp.sh/2rxid G>Суть простая: приложение поключается к набору серверов, на каждом сервере есть набор клиентов. Для каждого клиента есть объект, через который приложение с ним работает. Хочу сохранить это все в map<int,map<int,unique_ptr<X>>. Map будет владеть объкетами, при удалении map все объекты долны быть освобождены. G>При работе с таким мапом получаю шибку компиляции, которую не могу распарсить.
Ха! Я тут отпишусь вообще не по теме (по ней уже собственно всё хорошо ответили). Смотрю коллега gandjustas с которым мы не раз сталкивались на этом форуме в дискуссиях типа C++ vs C# и т.п., тоже решил попробовать на своём опыте, каково оно на тёмной стороне... Ну что же, добро пожаловать коллега!
Здравствуйте, alex_public, Вы писали:
_>Здравствуйте, gandjustas, Вы писали:
G>>Господа, подскажите что не так с этим кодом: http://cpp.sh/2rxid G>>Суть простая: приложение поключается к набору серверов, на каждом сервере есть набор клиентов. Для каждого клиента есть объект, через который приложение с ним работает. Хочу сохранить это все в map<int,map<int,unique_ptr<X>>. Map будет владеть объкетами, при удалении map все объекты долны быть освобождены. G>>При работе с таким мапом получаю шибку компиляции, которую не могу распарсить.
_>Ха! Я тут отпишусь вообще не по теме (по ней уже собственно всё хорошо ответили). Смотрю коллега gandjustas с которым мы не раз сталкивались на этом форуме в дискуссиях типа C++ vs C# и т.п., тоже решил попробовать на своём опыте, каково оно на тёмной стороне... Ну что же, добро пожаловать коллега!
Увы, не мой выбор. Делаю плагин к одной софтине, в плагине использую directx. В теории мог бы написать все на C#, но примеров кода на managed нет, а увязывать undamaged с managed выйдет сильно дольше, чем побороть C++ (по крайней мере так кажется). Кроме того требовательность к ресурсам у плагина должна быть минимальная.
Если хотя бы один из факторов отсутствовал — делал бы на C#.