Вставить в std::map без пустого конструктора по умолчанию

У меня есть экземпляры 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.

Использовать insert() вместо []?

Sam Varshavchik 13.04.2023 14:00

@SamVarshavchik очень похоже, если я правильно понимаю : Foo& foo = data.insert(std::make_pair(key, Foo(requirement))).first->second; а может Foo& foo = data.insert({key, Foo(requirement)}).first->second; ? Есть ли разница между emplace() и insert({}) ?

Caduchon 13.04.2023 14:09

Да, есть разница, которая должна быть объяснена в вашем учебнике C++, в котором также объясняется важная разница между [] и insert(). У вас есть учебник или какой-либо другой справочный материал, к которому вы можете обратиться за всеми подробностями?

Sam Varshavchik 13.04.2023 14:11

Если у вас нет веских причин не делать этого: используйте try_emplace, так как это единственная из подписей, которая будет работать с объектами, не допускающими операций перемещения/копирования. emplace и insert отличаются только тем, что insert ожидает std::make_pair, а emplace уже делает для вас.

Ext3h 13.04.2023 14:11

@SamVarshavchik, конечно, (пере)читать учебники - хорошая причина удалить stackoverflow из Интернета ... Мой учебник - en.cppreference.com, и он довольно громоздкий. Прочитав подробности, insert создаст 2 копии, если предоставленные типы шаблонов не совпадают с value_type. Я предпочитаю emplace.

Caduchon 13.04.2023 14:19

Значит, у тебя такой же учебник, как у меня. И как раз сегодня утром мне нужно было найти кое-что о диапазонах, и именно туда я отправился, вместо того, чтобы задавать вопрос здесь, потому что это не замена учебнику. Знание того, где искать и как читать техническую документацию, в значительной степени является обязательным навыком для каждого разработчика C++. И это не Stackoverflow, у которого другая цель: задавать вопросы, которые немного менее очевидны, чем то, на что можно ответить, просто просмотрев справочный материал.

Sam Varshavchik 13.04.2023 14:28

@SamVarshavchik Согласен. Но это субъективный вопрос. Чтение документации показывает, что есть несколько приемлемых решений. Мой вопрос требует некоторого опыта, этого нет (пока?) в документации, но опыт является основой stackoverflow. Существующие ветки обсуждения этого вопроса, указанные ниже, являются доказательством того, что это не решение «прочитать книгу».

Caduchon 13.04.2023 14:34

@Ext3h Вы можете сделать то же самое с emplace: godbolt.org/z/PhPdzWs5W. Однако try_emplace гораздо проще использовать для конструкторов с несколькими аргументами.

Daniel Langr 13.04.2023 14:48

@Caduchon Работает ли data.emplace(key, requirement) на вас? data.emplace(key, Foo(requirement)) включает ненужный вызов конструктора перемещения/копирования.

Daniel Langr 13.04.2023 14:50

@DanielLangr хо да! Это работает для меня. Для меня это простой способ, потому что мне не нужно указывать тип Foo в вызове (на самом деле я упростил: у меня много подобных классов Foo-like, все требуют передать *this при построении). Тогда я могу каждый раз заменять на data.emplace(key, *this).

Caduchon 13.04.2023 15:00
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
10
126
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Из здесь

Вызов этой функции эквивалентен:

(*((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, в вашем случае оба варианта фактически одинаковы.

Другие вопросы по теме