У меня есть экземпляры std::map<std::string,Foo>
, где я вставляю и заполняю данные. Класс Foo
является конструируемым по умолчанию, копируемым по умолчанию. Затем я делаю это таким (простым) способом:
std::map<std::string,Foo> data;
for(std::string const& key : keys)
{
assert(data.count(key) == 0); // it's assumed that the keys not already exist
Foo& foo = data[key];
foo.fill(blahblah);
}
После рефакторинга класс Foo
потерял пустой конструктор по умолчанию. Каковы (простые) альтернативы вставке и заполнению data
? На данный момент я заменил это, но я не знаю, является ли это лучшим/более простым способом рефакторинга кода:
std::map<std::string,Foo> data;
for(std::string const& key : keys)
{
assert(data.count(key) == 0); // it's assumed that the keys not already exist
Foo& foo = data.emplace(key, Foo(requirement)).first->second;
foo.fill(blahblah);
}
Я также думаю, что это возможно с insert
, insert_or_assign
, emplace
, emplace_hint
, try_emplace
. Я немного потерялся со всеми возможностями...
Меня интересуют решения для C++11 или C++17.
@SamVarshavchik очень похоже, если я правильно понимаю : Foo& foo = data.insert(std::make_pair(key, Foo(requirement))).first->second;
а может Foo& foo = data.insert({key, Foo(requirement)}).first->second;
? Есть ли разница между emplace()
и insert({})
?
Да, есть разница, которая должна быть объяснена в вашем учебнике C++, в котором также объясняется важная разница между []
и insert()
. У вас есть учебник или какой-либо другой справочный материал, к которому вы можете обратиться за всеми подробностями?
Если у вас нет веских причин не делать этого: используйте try_emplace
, так как это единственная из подписей, которая будет работать с объектами, не допускающими операций перемещения/копирования. emplace
и insert
отличаются только тем, что insert
ожидает std::make_pair
, а emplace
уже делает для вас.
@SamVarshavchik, конечно, (пере)читать учебники - хорошая причина удалить stackoverflow из Интернета ... Мой учебник - en.cppreference.com, и он довольно громоздкий. Прочитав подробности, insert
создаст 2 копии, если предоставленные типы шаблонов не совпадают с value_type. Я предпочитаю emplace
.
Значит, у тебя такой же учебник, как у меня. И как раз сегодня утром мне нужно было найти кое-что о диапазонах, и именно туда я отправился, вместо того, чтобы задавать вопрос здесь, потому что это не замена учебнику. Знание того, где искать и как читать техническую документацию, в значительной степени является обязательным навыком для каждого разработчика C++. И это не Stackoverflow, у которого другая цель: задавать вопросы, которые немного менее очевидны, чем то, на что можно ответить, просто просмотрев справочный материал.
@SamVarshavchik Согласен. Но это субъективный вопрос. Чтение документации показывает, что есть несколько приемлемых решений. Мой вопрос требует некоторого опыта, этого нет (пока?) в документации, но опыт является основой stackoverflow. Существующие ветки обсуждения этого вопроса, указанные ниже, являются доказательством того, что это не решение «прочитать книгу».
@Ext3h Вы можете сделать то же самое с emplace
: godbolt.org/z/PhPdzWs5W. Однако try_emplace
гораздо проще использовать для конструкторов с несколькими аргументами.
@Caduchon Работает ли data.emplace(key, requirement)
на вас? data.emplace(key, Foo(requirement))
включает ненужный вызов конструктора перемещения/копирования.
@DanielLangr хо да! Это работает для меня. Для меня это простой способ, потому что мне не нужно указывать тип Foo
в вызове (на самом деле я упростил: у меня много подобных классов Foo-like, все требуют передать *this
при построении). Тогда я могу каждый раз заменять на data.emplace(key, *this)
.
Из здесь
Вызов этой функции эквивалентен:
(*((this->insert(make_pair(k,mapped_type()))).first)).
Таким образом, буквальной заменой operator[]
в этом случае будет использование функции insert
. Однако я бы предложил использовать emplace
, чтобы избежать дополнительной конструкции std::pair
, которую функция insert
принимает в качестве аргумента.
Начиная с C++17, вы можете использовать try_emplace
, за исключением очень тонких случаев. Вы можете обратиться к этому обсуждению, чтобы узнать, относится ли предпочтение emplace
к try_emplace
к вашему случаю.
Вы можете использовать std::map::emplace вместе с std::move
, чтобы добавить элемент класса, в котором отсутствует конструктор по умолчанию. Это можно сделать после того, как он был инициализирован с помощью fill(...)
.
Пример:
#include <map>
#include <string>
#include <iostream>
struct Foo
{
Foo(int n) : m_n(n) {}
int m_n;
};
int main()
{
std::map<std::string, Foo> m;
// Create a Foo and insert it:
Foo foo{ 111 };
// foo.fill(blahblah);
m.emplace("aaa", std::move(foo));
// Verify it was inserted properly:
Foo& foo2 = m.at("aaa");
std::cout << foo2.m_n << std::endl;
return 0;
}
Выход:
111
Обратите внимание, что я использовал std::map::at, чтобы убедиться, что элемент был вставлен правильно (использование operator[]
не скомпилируется из-за отсутствия Foo
ctor по умолчанию).
Я бы просто пошел с:
data.emplace(key, requirement)
или, начиная с С++ 17, с
data.try_emplace(key, requirement)
Обычно лучше использовать try_emplace
(см. вопрос, упомянутый в другом ответе); однако, ASAIK, в вашем случае оба варианта фактически одинаковы.
Использовать
insert()
вместо[]
?