Qual è lo scopo di std :: make_pair vs il costruttore di std :: pair?


Risposte:


165

La differenza è che con std::pairte devi specificare i tipi di entrambi gli elementi, mentre std::make_paircreerai una coppia con il tipo di elementi che gli vengono passati, senza che tu debba dirlo. Questo è quello che potrei comunque raccogliere da vari documenti.

Vedi questo esempio da 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>

A parte il bonus di conversione implicito, se non avessi usato make_pair avresti dovuto farlo

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

ogni volta che lo assegni a uno, che sarebbe fastidioso nel tempo ...


1
In realtà, i tipi dovrebbero essere dedotti al momento della compilazione senza la necessità di specificare.
Chad,

@Tor Sì, so come usarli entrambi, ero solo curioso di sapere se ci fosse una ragione std::make_pair. Apparentemente è solo per comodità.

@Jay Sembrerebbe di sì.
Tor Valamo,

15
Penso che tu possa fare al one = {10, 20}giorno d'oggi, ma non ho un compilatore C ++ 11 a portata di mano per verificarlo.
Sali il

6
Nota anche che make_pairfunziona con tipi senza nome, tra cui strutture, sindacati, lambda e altri doodle.
Mooing Duck,

35

Come ha risposto @MSalters sopra, ora puoi usare le parentesi graffe per farlo in C ++ 11 (appena verificato con un compilatore C ++ 11):

pair<int, int> p = {1, 2};

28

Gli argomenti del modello di classe non possono essere dedotti dal costruttore prima di C ++ 17

Prima di C ++ 17 non si poteva scrivere qualcosa del genere:

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

poiché ciò dedurrebbe i tipi di modello dagli argomenti del costruttore.

C ++ 17 rende possibile questa sintassi e quindi make_pairridondante.

Prima di C ++ 17, std::make_pairci ha permesso di scrivere un codice meno dettagliato:

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

invece del più dettagliato:

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

che ripete i tipi e può essere molto lungo.

L'inferenza del tipo funziona in quel caso pre-C ++ 17 perché make_pair non è un costruttore.

make_pair è sostanzialmente 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);
}

Lo stesso concetto vale per insertervsinsert_iterator .

Guarda anche:

Esempio minimo

Per rendere le cose più concrete, possiamo osservare minimamente il problema 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);
}

poi:

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

si compila felicemente, ma:

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

fallisce con:

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

e richiede invece di funzionare:

MyClass<int> my_class(1);

o l'aiutante:

auto my_class = make_my_class(1);

che utilizza una funzione regolare anziché un costruttore.

Differenza per `std :: reference_wrapper

Questo commento lo menzionastd::make_pair svolgestd::reference_wrapper , mentre il costruttore non lo fa, così che è una differenza. Esempio TODO.

Testato con GCC 8.1.0, Ubuntu 16.04 .


1
"C ++ 17 rende possibile questa sintassi e quindi rende_pair ridondante." - Perché std::make_pairnon è diventato obsoleto in C ++ 17?
andreee

@andreee Non ne sono sicuro, la ragione possibile è che non crea problemi, quindi non è necessario rompere il vecchio codice? Ma non ho familiarità con la logica del comitato C ++, chiamami se trovi qualcosa.
Ciro Santilli 6 冠状 病 六四 事件 法轮功

1
Una cosa utile che ho riscontrato è che la possibilità di specificare i tipi con std :: make_pair <T1, T2> (o1, o2) impedisce all'utente di commettere l'errore di passare i tipi o1 o o2 che non possono essere implicitamente cast su T1 o T2. Ad esempio, passando un numero negativo a un int senza segno. -Wsign-conversion -Werror non rileverà questo errore con il costruttore std :: pair in c ++ 11, tuttavia rileverà l'errore se viene utilizzato std :: make_pair.
conchoecia,

make_pairscartare i wrapper di riferimento, quindi in realtà è diverso dal CTAD.
LF

26

Non vi è alcuna differenza tra l'utilizzo make_paire la chiamata esplicita al paircostruttore con argomenti di tipo specificati. std::make_pairè più conveniente quando i tipi sono dettagliati perché un metodo modello ha la deduzione del tipo in base ai parametri indicati. Per esempio,

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));

21

Vale la pena notare che questo è un linguaggio comune nella programmazione di modelli C ++. È noto come il linguaggio del generatore di oggetti, puoi trovare maggiori informazioni e un bell'esempio qui .

modificare Come suggerito da qualcuno nei commenti (da quando rimosso), il seguente è un estratto leggermente modificato dal collegamento in caso di interruzione.

Un generatore di oggetti consente la creazione di oggetti senza specificare esplicitamente i loro tipi. Si basa su un'utile proprietà dei modelli di funzione che i modelli di classe non hanno: I parametri di tipo di un modello di funzione vengono dedotti automaticamente dai suoi parametri effettivi. std::make_pairè un semplice esempio che restituisce un'istanza del std::pairmodello in base ai parametri effettivi della std::make_pairfunzione.

template <class T, class U>
std::pair <T, U> 
make_pair(T t, U u)
{
  return std::pair <T, U> (t,u);
}

2
@duck In realtà &&dal C ++ 11.
Justme0,

5

make_pair crea una copia aggiuntiva sul costruttore diretto. Ho sempre scritto le mie coppie per fornire una sintassi semplice.
Questo mostra la differenza (esempio di 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;
}

4
Sono abbastanza sicuro che la copia aggiuntiva verrà elusa in tutti i casi, se le impostazioni di ottimizzazione del compilatore sono abbastanza alte.
Björn Pollex,

1
Perché mai vorresti affidarti alle ottimizzazioni del compilatore per la correttezza?
sjbx,

Ottengo gli stessi risultati con entrambe le versioni e con std::movesolo dentro inserte / o intorno a quale sarebbe un riferimento sample. È solo quando cambio std::map<int,Sample>a std::map<int,Sample const&>che riduco il numero di oggetti costruiti, e solo quando cancello il costruttore di copie che elimino tutte le copie (ovviamente). Dopo aver apportato entrambe queste modifiche, il mio risultato include una chiamata al costruttore predefinito e due chiamate al distruttore per lo stesso oggetto. Penso che mi debba mancare qualcosa. (g ++ 5.4.1, c ++ 11)
John P

FWIW Sono d'accordo sul fatto che l'ottimizzazione e la correttezza dovrebbero essere completamente indipendenti, poiché questo è esattamente il tipo di codice che scrivi come controllo di integrità dopo che diversi livelli di ottimizzazione producono risultati incoerenti. In generale, consiglierei emplaceinvece insertse stai solo costruendo un valore da inserire immediatamente (e non vuoi istanze extra.) Non è la mia area di competenza, se posso anche dire di averne uno, ma la copia / sposta la semantica introdotta da C ++ 11 mi ha aiutato molto.
John P,

Credo di aver riscontrato esattamente lo stesso problema e dopo aver eseguito il debug per circa l'intera serata, sono finalmente arrivato.
lllllllllllll

1

a partire da c ++ 11 basta usare l'inizializzazione uniforme per le coppie. Quindi invece di:

std::make_pair(1, 2);

o

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

basta usare

{1, 2};

{1, 2}può essere utilizzato per inizializzare una coppia, ma non esegue il commit per la coppia di tipi. Vale a dire quando si utilizza auto è necessario impegnarsi a un tipo sul RHS: auto p = std::pair{"Tokyo"s, 9.00};.
Markus,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.