¿Hay alguna razón para usar std::map::emplace() en lugar de try_emplace() en C++1z?

4 minutos de lectura

avatar de usuario
s3rvac

En C++17, std::map y std::unordered_map obtuve una nueva plantilla de función de miembro: try_emplace(). Esta nueva incorporación, propuesta en n4279se comporta de manera similar a emplace()pero tiene las siguientes ventajas:

  • try_emplace() no se mueve de los argumentos rvalue si la inserción no ocurre. Esto es útil cuando se manipulan mapas cuyos valores son tipos de solo movimiento, como std::unique_ptr.
  • try_emplace() trata la clave y los argumentos de la mapped_type por separado, lo que lo hace algo más intuitivo que los mutadores genéricos que se expresan en términos de value_type (cual es std::pair).

Dadas las ventajas anteriores, ¿usaría alguna vez emplace() de C++ 11 en lugar de try_emplace() de C++1z al escribir código solo de C++1z?

  • emplace podría ser unos ciclos más rápido porque no comprueba si se producen inserciones, pero esa no es realmente una buena razón.

    –Henri Menke

    5 de septiembre de 2017 a las 4:12

  • con la ventaja añadida de try_emplace, siempre preferiría esto a emplace

    –Hariom Singh

    5 de septiembre de 2017 a las 4:40

  • @HenriMenke: emplace() comprueba si hay duplicados, después construcción de la value_type. Es mucho menos eficiente si se encuentra un duplicado, porque se requiere para realizar una asignación (y desasignación en caso de duplicado).

    – Arne Vogel

    5 de septiembre de 2017 a las 8:51

  • También debe tenerse en cuenta emplace es compatible con más contenedores con una semántica más general (cf. requisitos en diferentes tipos de contenedores). Entonces, en código genérico, no necesariamente dependiendo de std::map o std::unordered_map, emplace tiene más flexibilidad sobre try_emplace.

    – FrankHB

    27 de diciembre de 2018 a las 1:26

try_emplace de hecho puede reemplazar la mayoría de los usos de emplacepero si tiene un caso de uso inusual de un map con un tipo de clave no copiable e inamovible, try_emplace no funcionará porque copia o mueve la llave. En ese caso, debe utilizar emplace con std::pairConstructor de construcción por partes para evitar copias y movimientos.

Incluso si su tipo de clave es copiable y/o móvil, la construcción por partes es la única forma de evitar copiar o mover la construcción de la clave, por lo que puede haber casos en los que prefiera eso a try_emplace.

  • Estas diciendo eso emplace con piecewise_construct siempre es mejor que try_emplace? ¿Cuál es el punto de try_emplace en absoluto entonces?

    – Power Gamer

    19 de diciembre de 2019 a las 15:03

  • No creo que eso sea lo que dice Praetorian. Ver la pregunta por razones para usar try_emplace.

    – Carlos G.

    4 de febrero de 2020 a las 0:54

try_emplace tampoco admite la búsqueda heterogénea; no puede, porque toma la clave.

Supongamos que tenemos un std::map<std::string, int, std::less<>> counts; y un std::string_view sv. Quiero hacer el equivalente de ++counts[std::string(sv)];pero no quiero crear un temporal std::stringlo cual es un desperdicio, especialmente si la cadena ya está presente en el mapa. try_emplace no puedo ayudarte allí. En su lugar, harías algo como

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

avatar de usuario
hariom singh

Siempre preferiría try_emplace a emplace. Una diferencia crucial es que try_emplace no construirá el objeto asociado con la clave, si la clave ya existe. Esto aumentará el rendimiento en caso de que los objetos de ese tipo sean costosos de crear.

Por ejemplo, el siguiente código (Ejemplo de https://github.com/PacktPublishing/Cpp17-STL-Cookbook/blob/master/Chapter02/ficient_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";
    }
}

Para el código anterior

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

si hay una inserción fallida, los pares no se construirán, lo que se suma al rendimiento

  • Entonces, ¿qué haces cuando tienes uno de los ejemplos de las respuestas de @Praetorian y @ TC?

    – Don Escotilla

    5 de agosto de 2019 a las 22:39

¿Ha sido útil esta solución?