Was ist der Zweck von std :: make_pair gegenüber dem Konstruktor von std :: pair?

180

Was ist der Zweck von std::make_pair?

Warum nicht einfach tun std::pair<int, char>(0, 'a')?

Gibt es einen Unterschied zwischen den beiden Methoden?

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
quelle
6
In C ++ 11 können Sie fast vollständig auf make_pair verzichten. Siehe meine Antwort .
PlagueHammer
2
In C ++ 17 std::make_pairist redundant. Es gibt eine Antwort unten, die dies detailliert beschreibt.
Drew Dormann

Antworten:

165

Der Unterschied besteht darin, dass std::pairSie die Typen beider Elemente angeben müssen, während std::make_pairein Paar mit dem Typ der Elemente erstellt wird, die an das Element übergeben werden, ohne dass Sie es angeben müssen. Das könnte ich sowieso aus verschiedenen Dokumenten entnehmen.

Siehe dieses Beispiel unter 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>

Abgesehen vom impliziten Conversion-Bonus müssten Sie dies tun, wenn Sie make_pair nicht verwenden würden

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

jedes Mal, wenn Sie einem zugewiesen haben, was mit der Zeit ärgerlich wäre ...

Tor Valamo
quelle
1
Tatsächlich sollten die Typen zur Kompilierungszeit abgeleitet werden, ohne dass eine Angabe erforderlich ist.
Tschad
@Tor Ja, ich weiß, wie man beide benutzt, ich war nur neugierig, ob es einen Grund dafür gibt std::make_pair. Anscheinend ist es nur zur Vereinfachung.
@ Jay Es scheint so.
Tor Valamo
15
Ich denke, Sie können dies one = {10, 20}heutzutage tun , aber ich habe keinen C ++ 11-Compiler zur Hand, um dies zu überprüfen.
MSalters
6
Beachten Sie auch, dass dies make_pairmit unbenannten Typen funktioniert, einschließlich Strukturen, Gewerkschaften, Lambdas und anderen Doodads.
Mooing Duck
35

Wie @MSalters oben geantwortet hat, können Sie jetzt in C ++ 11 geschweifte Klammern verwenden (dies wurde gerade mit einem C ++ 11-Compiler überprüft):

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

Klassenvorlagenargumente konnten vor C ++ 17 nicht vom Konstruktor abgeleitet werden

Vor C ++ 17 konnte man so etwas nicht schreiben:

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

da dies Vorlagentypen aus den Konstruktorargumenten ableiten würde.

C ++ 17 macht diese Syntax möglich und daher make_pairredundant.

Vor C ++ 17 std::make_pairdurften wir weniger ausführlichen Code schreiben:

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

statt der ausführlicheren:

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

Das wiederholt die Typen und kann sehr lang sein.

Die Typinferenz funktioniert in diesem Fall vor C ++ 17, da make_paires sich nicht um einen Konstruktor handelt.

make_pair ist im Wesentlichen gleichbedeutend mit:

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

Das gleiche Konzept gilt für insertervs insert_iterator.

Siehe auch:

Minimales Beispiel

Um die Dinge konkreter zu machen, können wir das Problem minimal beobachten mit:

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

dann:

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

kompiliert gerne, aber:

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

schlägt fehl mit:

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

und erfordert stattdessen zu arbeiten:

MyClass<int> my_class(1);

oder der Helfer:

auto my_class = make_my_class(1);

die eine reguläre Funktion anstelle eines Konstruktors verwendet.

Unterschied für `std :: reference_wrapper

In diesem Kommentar wird erwähnt, dass das std::make_pairEntpacken erfolgt, std::reference_wrapperwährend der Konstruktor dies nicht tut. Das ist also ein Unterschied. TODO Beispiel.

Getestet mit GCC 8.1.0, Ubuntu 16.04 .

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
quelle
1
"C ++ 17 macht diese Syntax möglich und macht make_pair daher redundant." - Warum ist es std::make_pairin C ++ 17 nicht veraltet?
Andreee
@andreee Ich bin mir nicht sicher, möglicher Grund ist, dass es keine Probleme verursacht, also keine Notwendigkeit, alten Code zu brechen? Aber ich bin nicht mit den Gründen des C ++ - Komitees vertraut. Pingen Sie mich an, wenn Sie etwas finden.
Ciro Santilli 6 冠状 病 六四.
1
Eine nützliche Sache, auf die ich gestoßen bin, ist, dass die Möglichkeit, die Typen mit std :: make_pair <T1, T2> (o1, o2) anzugeben, den Benutzer daran hindert, den Fehler zu machen, die Typen o1 oder o2 zu übergeben, die nicht implizit sein können auf T1 oder T2 werfen. Zum Beispiel eine negative Zahl an ein vorzeichenloses int übergeben. -Wsign-convert -Werror fängt diesen Fehler mit dem Konstruktor std :: pair in c ++ 11 nicht ab, fängt jedoch den Fehler ab, wenn std :: make_pair verwendet wird.
Conchoecia
make_pairpackt Referenz-Wrapper aus, unterscheidet sich also tatsächlich von CTAD.
LF
26

Es gibt keinen Unterschied zwischen der Verwendung make_pairund dem expliziten Aufruf des pairKonstruktors mit angegebenen Typargumenten. std::make_pairDies ist praktischer, wenn die Typen ausführlich sind, da eine Vorlagenmethode eine Typableitung basierend auf den angegebenen Parametern aufweist. Beispielsweise,

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));
Teufel
quelle
21

Es ist erwähnenswert, dass dies eine gängige Redewendung in der C ++ - Vorlagenprogrammierung ist. Es ist als Objektgenerator-Sprache bekannt. Weitere Informationen und ein schönes Beispiel finden Sie hier .

Bearbeiten Wie in den Kommentaren vorgeschlagen (seitdem entfernt), ist das Folgende ein leicht modifizierter Auszug aus dem Link, falls er kaputt geht.

Ein Objektgenerator ermöglicht die Erstellung von Objekten, ohne deren Typ explizit anzugeben. Es basiert auf einer nützlichen Eigenschaft von Funktionsvorlagen, die Klassenvorlagen nicht haben: Die Typparameter einer Funktionsvorlage werden automatisch aus ihren tatsächlichen Parametern abgeleitet. std::make_pairist ein einfaches Beispiel, das std::pairabhängig von den tatsächlichen Parametern der std::make_pairFunktion eine Instanz der Vorlage zurückgibt .

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

make_pair erstellt eine zusätzliche Kopie über den direkten Konstruktor. Ich tippe meine Paare immer ein, um eine einfache Syntax bereitzustellen.
Dies zeigt den Unterschied (Beispiel von 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
quelle
4
Ich bin mir ziemlich sicher, dass die zusätzliche Kopie in allen Fällen entfernt wird, wenn die Optimierungseinstellungen des Compilers hoch genug sind.
Björn Pollex
1
Warum sollten Sie sich jemals auf Compiler-Optimierungen verlassen wollen, um die Richtigkeit zu gewährleisten?
sjbx
Ich erhalte die gleichen Ergebnisse mit beiden Versionen und mit std::movenur innerhalb insertund / oder um das, worauf ein Verweis wäre sample. Es ist nur , wenn ich ändern std::map<int,Sample>zu , std::map<int,Sample const&>dass ich die Anzahl der konstruierten Objekte zu reduzieren, und nur , wenn ich den Copykonstruktor löschen , dass ich alle Kopien beseitigen (offensichtlich). Nachdem ich diese beiden Änderungen vorgenommen habe, enthält mein Ergebnis einen Aufruf des Standardkonstruktors und zwei Aufrufe des Destruktors für dasselbe Objekt. Ich denke, ich muss etwas vermissen. (g ++ 5.4.1, c ++ 11)
John P
FWIW Ich stimme zu, dass Optimierung und Korrektheit völlig unabhängig sein sollten, da dies genau die Art von Code ist, die Sie als Überprüfung der Integrität schreiben, nachdem verschiedene Optimierungsstufen zu inkonsistenten Ergebnissen führen. Im Allgemeinen würde ich empfehlen, emplaceanstatt insertnur einen Wert zu erstellen, der sofort eingefügt werden soll (und Sie möchten keine zusätzlichen Instanzen). Es ist nicht mein Fachgebiet, wenn ich überhaupt sagen kann, dass ich einen habe, sondern das Kopieren / Verschieben Die von C ++ 11 eingeführte Semantik hat mir sehr geholfen.
John P
Ich glaube, ich habe genau das gleiche Problem und nachdem ich den ganzen Abend über debuggt hatte, kam ich endlich hierher.
lllllllllllll
1

Verwenden Sie ab C ++ 11 nur eine einheitliche Initialisierung für Paare. Also statt:

std::make_pair(1, 2);

oder

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

benutz einfach

{1, 2};
Mahmoud Badri
quelle
{1, 2}kann zum Initialisieren eines Paares verwendet werden, wird jedoch nicht für ein Typpaar festgeschrieben. Das heißt, wenn Sie auto verwenden, müssen Sie sich auf einen Typ auf der rechten Seite festlegen : auto p = std::pair{"Tokyo"s, 9.00};.
Markus