Как вставить/поместить на карту, чтобы не создавать временные объекты?

Я читаю рекомендацию из одной из книг С++ 11 предпочесть emplace вместо insert при добавлении элементов в контейнер, чтобы избежать создания временных объектов (вызовы конструкторов/деструкторов вставляемых объектов). Но я немного сбит с толку, потому что есть несколько возможностей добавить объект на карту, например.

#include <iostream>
#include <string>
#include <cstdint>
#include <map>

int main()
{
    std::string one     { "one" };
    std::string two     { "two" };

    std::map<uint32_t, std::string> testMap;

    testMap.insert(std::make_pair(1, one));         // 1
    testMap.emplace(2, two);                        // 2
    testMap.insert(std::make_pair(3, "three"));     // 3
    testMap.emplace(4, "four");                     // 4

    using valType = std::map < uint32_t, std::string >::value_type;
    testMap.emplace(valType(5, "five"));            // 5
    testMap.insert(valType(6, "six"));              // 6

    return 0;
}

Здесь также задействованы некоторые скрытые механизмы, которые не видны сразу при чтении такого кода — идеальная переадресация, неявные преобразования...

Каков оптимальный способ добавления элементов в контейнер карты?


person tommyk    schedule 21.01.2015    source источник


Ответы (1)


Давайте рассмотрим ваши варианты по одному (плюс один или два, которые вы не упомянули).

Варианты 1 и 6 практически идентичны с точки зрения семантики. using и pair — это просто два разных способа написания value_type для map. Если вы хотите, вы можете добавить третий способ, используя оператор typedef вместо оператора using:

typedef std::map<uint32_t, std::string>::value_type valType;

... и иметь эквивалент C++98/03 вашего #6. Все три в конечном итоге делают одно и то же: создают временный объект типа pair и вставляют его в файл map.

Версии 3 и 5 делают почти то же самое. Они используют emplace, но то, что они передают, уже является объектом value_type map. К моменту начала выполнения самого emplace тип объекта, который будет храниться в карте, уже создан. Опять же, единственная разница между ними заключается в синтаксисе, используемом для указания типа pair, и, опять же, с typedef, как я показал выше, вы могли бы получить C++98/03, эквивалентный тому, который в настоящее время имеет оператор using. Тот факт, что в версии 3 используется insert, а в версии 5 — emplace, практически не имеет значения — к моменту вызова любой из функций-членов мы уже создали и передали временный объект.

Варианты 2 и 4 на самом деле используют emplace больше, чем это, вероятно, было задумано — передача отдельных компонентов, совершенная переадресация их в конструктор и создание объекта value_type на месте, поэтому мы избегаем создания каких-либо временных объектов в любой момент. Основное (единственное?) различие между ними заключается в том, является ли вещь, которую мы передаем для компонента string value_type, строковым литералом (из которого необходимо создать временный объект std::string) или объектом std::string, который был создан заранее. времени.

Выбор между ними может быть нетривиальным. Если (как указано выше) вы делаете это только один раз, на самом деле это не будет иметь никакого значения — независимо от того, когда вы его создаете, вы создаете строковый объект, а затем помещаете его в map.

Таким образом, чтобы действительно изменить ситуацию, нам нужно предварительно создать строковый объект, а затем несколько раз вставить тот же самый строковый объект в файл map. Это довольно необычно само по себе — в большинстве случаев вы будете делать что-то вроде считывания внешних данных в строку, а затем вставлять их в map. Если вы действительно неоднократно вставляете (созданный из std::string) один и тот же строковый литерал, велики шансы, что любой разумный компилятор сможет определить, что результирующая строка является инвариантной к циклу, и выведет конструкцию string из цикла, дав, по сути, одно и то же эффект.

Итог: что касается использования самого map, варианты 2 и 4 эквивалентны. Между этими двумя я бы не стал прилагать никаких реальных усилий, чтобы использовать вариант 2 вместо варианта 4 (т. е. предварительное создание строки), но это, вероятно, произойдет в большинстве случаев просто потому, что вставка одного строкового литерала в карту редко полезно. Строка, которую вы помещаете в карту, гораздо чаще будет поступать из какого-то внешнего источника данных, поэтому у вас будет string, потому что это то, что (например) std::getline дало вам, когда вы читаете данные из файла.

person Jerry Coffin    schedule 21.01.2015