Есть ли причина использовать std:: map:: emplace() вместо try_emplace() в С++ 1z?

В С++ 17 std::map и std::unordered_map получили новый шаблон функции-члена: try_emplace(). Это новое дополнение, предложенное в n4279, ведет себя аналогично emplace(), но имеет следующие преимущества:

  • try_emplace() не перемещается из аргументов rvalue, если вставка не происходит. Это полезно при работе с картами, значения которых являются типами только для перемещения, такими как std::unique_ptr.
  • try_emplace() обрабатывает ключ и аргументы mapped_type отдельно, что делает его несколько более интуитивным, чем универсальные мутаторы, которые выражаются в терминах value_type (который является std::pair).

Учитывая вышеперечисленные преимущества, вы когда-нибудь использовали бы emplace() из С++ 11 вместо try_emplace() из С++ 1z при написании кода только для С++ 1z?

Ответы

Ответ 1

try_emplace действительно может заменить большинство применений emplace, но если у вас есть необычный случай использования map с типом не копируемого и неподвижного ключа, try_emplace не будет работать, потому что он копирует или перемещает ключ. В этом случае вы должны использовать emplace с std::pair кусочным конструктором конструкции, чтобы избежать копирования и перемещения.

Даже если ваш тип ключа можно копировать и/или перемещать, кусочная конструкция - это единственный способ избежать копирования или перемещения конструирования ключа, поэтому могут быть случаи, когда вы предпочитаете, чтобы над try_emplace.

Ответ 2

try_emplace также не поддерживает гетерогенный поиск - он не может, поскольку он принимает ключ.

Предположим, что a std::map<std::string, int, std::less<>> counts; и a std::string_view sv. Я хочу сделать эквивалент ++counts[std::string(sv)];, но я не хочу создавать временный std::string, который просто расточительный, особенно если строка уже присутствует на карте. try_emplace не может вам помочь. Вместо этого вы бы сделали что-то вроде

if(auto lb = counts.lower_bound(sv); lb != counts.end() && lb->first == sv) {
    ++lb->second;
}
else {
    counts.emplace_hint(lb, sv, 1);
}

Ответ 3

Я бы всегда предпочел try_emplace, а не emplace. Принципиальное отличие состоит в том, что try_emplace не будет создавать объект, связанный с ключом, если ключ уже существует. Это повысит производительность в случае, если создание объектов этого типа является дорогостоящим

Например, приведенный ниже код (пример с https://github.com/PacktPublishing/Cpp17-STL-Cookbook/blob/master/Chapter02/efficient_insert_or_reassign_to_map.cpp)

#include <iostream>
#include <functional>
#include <list>
#include <map>

using namespace std;

struct billionaire {
    string name;
    double dollars;
    string country;
};

int main()
{
    list<billionaire> billionaires {
        {"Bill Gates", 86.0, "USA"},
        {"Warren Buffet", 75.6, "USA"},
        {"Jeff Bezos", 72.8, "USA"},
        {"Amancio Ortega", 71.3, "Spain"},
        {"Mark Zuckerberg", 56.0, "USA"},
        {"Carlos Slim", 54.5, "Mexico"},
        // ...
        {"Bernard Arnault", 41.5, "France"},
        // ...
        {"Liliane Bettencourt", 39.5, "France"},
        // ...
        {"Wang Jianlin", 31.3, "China"},
        {"Li Ka-shing", 31.2, "Hong Kong"}
        // ...
    };

    map<string, pair<const billionaire, size_t>> m;

    for (const auto &b : billionaires) {
        auto [iterator, success] = m.try_emplace(b.country, b, 1);

        if (!success) {
            iterator->second.second += 1;
        }
    }


    for (const auto & [key, value] : m) {
        const auto &[b, count] = value;

        cout << b.country << " : " << count << " billionaires. Richest is "
        << b.name << " with " << b.dollars << " B$\n";
    }
}

Для приведенного выше кода

m.try_emplace(b.country, b, 1);

если будет неудачная вставка, пары не будут созданы, что повышает производительность