¿Cuál es el propósito de std :: make_pair vs el constructor de std :: pair?

180

¿Para qué sirve std::make_pair?

¿Por qué no simplemente hacer std::pair<int, char>(0, 'a')?

¿Hay alguna diferencia entre los dos métodos?

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fuente
66
En C ++ 11, puede prescindir casi por completo sin make_pair. Mira mi respuesta .
PlagueHammer
2
En C ++ 17, std::make_paires redundante. Hay una respuesta a continuación que detalla esto.
Drew Dormann

Respuestas:

165

La diferencia es que std::pairnecesita especificar los tipos de ambos elementos, mientras std::make_pairque creará un par con el tipo de elementos que se le pasan, sin necesidad de contarlo. Eso es lo que podría reunir de varios documentos de todos modos.

Vea este ejemplo de http://www.cplusplus.com/reference/std/utility/make_pair/

pair <int,int> one;
pair <int,int> two;

one = make_pair (10,20);
two = make_pair (10.5,'A'); // ok: implicit conversion from pair<double,char>

Aparte de la bonificación de conversión implícita, si no usaras make_pair, deberías hacer

one = pair<int,int>(10,20)

cada vez que asignas a uno, lo que sería molesto con el tiempo ...

Tor Valamo
fuente
1
En realidad, los tipos deben deducirse en tiempo de compilación sin la necesidad de especificar.
Chad
@ Tor Sí, sé cómo usarlos a ambos, tenía curiosidad por saber si había una razón para hacerlo std::make_pair. Aparentemente es solo por conveniencia.
@ Jay Parece que sí.
Tor Valamo
15
Creo que puedes hacerlo one = {10, 20}hoy en día, pero no tengo un compilador de C ++ 11 a mano para comprobarlo.
MSalters
66
También tenga en cuenta que make_pairfunciona con tipos sin nombre, incluidas estructuras, uniones, lambdas y otros objetos.
Mooing Duck
35

Como @MSalters respondió anteriormente, ahora puede usar llaves para hacer esto en C ++ 11 (acaba de verificar esto con un compilador de C ++ 11):

pair<int, int> p = {1, 2};
Martillo de peste
fuente
28

Los argumentos de la plantilla de clase no se pudieron inferir del constructor antes de C ++ 17

Antes de C ++ 17 no podías escribir algo como:

std::pair p(1, 'a');

ya que eso inferiría tipos de plantillas a partir de los argumentos del constructor.

C ++ 17 hace que esa sintaxis sea posible y, por lo tanto, make_pairredundante.

Antes de C ++ 17, std::make_pairnos permitía escribir código menos detallado:

MyLongClassName1 o1;
MyLongClassName2 o2;
auto p = std::make_pair(o1, o2);

en lugar del más detallado:

std::pair<MyLongClassName1,MyLongClassName2> p{o1, o2};

que repite los tipos y puede ser muy largo.

La inferencia de tipos funciona en ese caso anterior a C ++ 17 porque make_pairno es un constructor.

make_pair es esencialmente equivalente a:

template<class T1, class T2>
std::pair<T1, T2> my_make_pair(T1 t1, T2 t2) {
    return std::pair<T1, T2>(t1, t2);
}

El mismo concepto se aplica a inserterfrente insert_iterator.

Ver también:

Ejemplo mínimo

Para hacer las cosas más concretas, podemos observar el problema mínimamente con:

main.cpp

template <class MyType>
struct MyClass {
    MyType i;
    MyClass(MyType i) : i(i) {}
};

template<class MyType>
MyClass<MyType> make_my_class(MyType i) {
    return MyClass<MyType>(i);
}

int main() {
    MyClass<int> my_class(1);
}

luego:

g++-8 -Wall -Wextra -Wpedantic -std=c++17 main.cpp

compila felizmente, pero:

g++-8 -Wall -Wextra -Wpedantic -std=c++14 main.cpp

falla con:

main.cpp: In function int main()’:
main.cpp:13:13: error: missing template arguments before my_class
     MyClass my_class(1);
             ^~~~~~~~

y requiere en cambio trabajar:

MyClass<int> my_class(1);

o el ayudante:

auto my_class = make_my_class(1);

que usa una función regular en lugar de un constructor.

Diferencia para `std :: reference_wrapper

Este comentario menciona que se std::make_pairdesenvuelve std::reference_wrappermientras que el constructor no, así que esa es una diferencia. TODO ejemplo.

Probado con GCC 8.1.0, Ubuntu 16.04 .

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
fuente
1
"C ++ 17 hace posible esa sintaxis y, por lo tanto, make_pair es redundante". - ¿Por qué no std::make_pairquedó obsoleto en C ++ 17?
andreee
@andreee No estoy seguro, ¿la posible razón es que no crea problemas, por lo que no es necesario romper el código antiguo? Pero no estoy familiarizado con la lógica del comité de C ++, hágame un ping si encuentra algo.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
1
Una cosa útil que he encontrado es que poder especificar los tipos con std :: make_pair <T1, T2> (o1, o2) evita que el usuario cometa el error de pasar los tipos o1 u o2 que no pueden ser implícitamente emitido a T1 o T2. Por ejemplo, pasar un número negativo a un int sin signo. -Wsign-conversion -Werror no detectará este error con el constructor std :: pair en c ++ 11, sin embargo, detectará el error si se utiliza std :: make_pair.
conchoecia
make_pairdesenvuelve envoltorios de referencia, por lo que es diferente de CTAD en realidad.
LF
26

No hay diferencia entre usar make_pairy llamar explícitamente al pairconstructor con argumentos de tipo especificados. std::make_paires más conveniente cuando los tipos son detallados porque un método de plantilla tiene una deducción de tipos basada en sus parámetros dados. Por ejemplo,

std::vector< std::pair< std::vector<int>, std::vector<int> > > vecOfPair;
std::vector<int> emptyV;

// shorter
vecOfPair.push_back(std::make_pair(emptyV, emptyV));

 // longer
vecOfPair.push_back(std::pair< std::vector<int>, std::vector<int> >(emptyV, emptyV));
diablo
fuente
21

Vale la pena señalar que este es un idioma común en la programación de plantillas de C ++. Es conocido como el idioma del generador de objetos, puedes encontrar más información y un buen ejemplo aquí .

Editar Como alguien sugirió en los comentarios (desde que se eliminó), el siguiente es un extracto ligeramente modificado del enlace en caso de que se rompa.

Un generador de objetos permite la creación de objetos sin especificar explícitamente sus tipos. Se basa en una propiedad útil de las plantillas de función que las plantillas de clase no tienen: los parámetros de tipo de una plantilla de función se deducen automáticamente de sus parámetros reales. std::make_paires un ejemplo simple que devuelve una instancia de la std::pairplantilla según los parámetros reales de la std::make_pairfunción.

template <class T, class U>
std::pair <T, U> 
make_pair(T t, U u)
{
  return std::pair <T, U> (t,u);
}
mkm
fuente
2
@duck En realidad &&desde C ++ 11.
Justme0
5

make_pair crea una copia adicional sobre el constructor directo. Siempre escribo dedef mis pares para proporcionar una sintaxis simple.
Esto muestra la diferencia (ejemplo de Rampal Chaudhary):

class Sample
{
    static int _noOfObjects;

    int _objectNo;
public:
    Sample() :
        _objectNo( _noOfObjects++ )
    {
        std::cout<<"Inside default constructor of object "<<_objectNo<<std::endl;
    }

    Sample( const Sample& sample) :
    _objectNo( _noOfObjects++ )
    {
        std::cout<<"Inside copy constructor of object "<<_objectNo<<std::endl;
    }

    ~Sample()
    {
        std::cout<<"Destroying object "<<_objectNo<<std::endl;
    }
};
int Sample::_noOfObjects = 0;


int main(int argc, char* argv[])
{
    Sample sample;
    std::map<int,Sample> map;

    map.insert( std::make_pair( 1, sample) );
    //map.insert( std::pair<int,Sample>( 1, sample) );
    return 0;
}
EmpZoooli
fuente
44
Estoy bastante seguro de que la copia adicional se eliminará en todos los casos, si la configuración de optimización del compilador es lo suficientemente alta.
Björn Pollex
1
¿Por qué querrías confiar en las optimizaciones del compilador para la corrección?
sjbx
Obtengo los mismos resultados con ambas versiones, y std::movesolo dentro inserty / o alrededor de lo que sería una referencia sample. Solo cuando cambio std::map<int,Sample>a std::map<int,Sample const&>eso reduzco el número de objetos construidos, y solo cuando elimino el constructor de copias elimino todas las copias (obviamente). Después de hacer ambos cambios, mi resultado incluye una llamada al constructor predeterminado y dos llamadas al destructor para el mismo objeto. Creo que me falta algo. (g ++ 5.4.1, c ++ 11)
John P
FWIW Estoy de acuerdo en que la optimización y la corrección deben ser completamente independientes, ya que este es exactamente el tipo de código que escribes como una verificación de sanidad después de que diferentes niveles de optimización produzcan resultados inconsistentes. En general, recomendaría en emplacelugar de insertsi solo está construyendo un valor para insertar de inmediato (y no desea instancias adicionales). No es mi área de especialización, incluso si puedo decir que tengo uno, sino copiar / mover La semántica introducida por C ++ 11 me ha ayudado mucho.
John P
Creo que me encuentro exactamente con el mismo problema y después de depurar durante toda la noche, finalmente vine aquí.
lllllllllllll
1

a partir de c ++ 11 solo use la inicialización uniforme para pares. Entonces en lugar de:

std::make_pair(1, 2);

o

std::pair<int, int>(1, 2);

Solo usa

{1, 2};
Mahmoud Badri
fuente
{1, 2}se puede usar para inicializar un par, pero no se compromete para el tipo de par. Es decir, cuando el uso de auto que tiene que comprometerse a un tipo en el lado derecho: auto p = std::pair{"Tokyo"s, 9.00};.
Markus