Jaki jest cel std :: make_pair w porównaniu z konstruktorem std :: pair?

180

Jaki jest cel std::make_pair?

Dlaczego po prostu nie zrobić std::pair<int, char>(0, 'a')?

Czy jest jakaś różnica między tymi dwiema metodami?

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
źródło
6
W C ++ 11 możesz prawie całkowicie obejść się bez make_pair. Zobacz moją odpowiedź .
PlagueHammer
2
W C ++ 17 std::make_pairjest zbędny. Poniżej znajduje się odpowiedź, która szczegółowo to wyjaśnia.
Drew Dormann

Odpowiedzi:

165

Różnica polega na tym, std::pairże musisz określić typy obu elementów, podczas gdy std::make_pairutworzysz parę z typem elementów, które są do niego przekazywane, bez konieczności mówienia. Tak czy inaczej mogłem zebrać z różnych dokumentów.

Zobacz ten przykład z 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>

Oprócz niejawnej premii za konwersję, jeśli nie używasz make_pair, musisz to zrobić

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

za każdym razem, gdy przydzielasz do jednego, co byłoby denerwujące z czasem ...

Tor Valamo
źródło
1
W rzeczywistości typy powinny być wywnioskowane w czasie kompilacji bez potrzeby określania.
Czad,
@Tor Tak, wiem, jak używać obu z nich, byłem po prostu ciekawy, czy był ku temu powód std::make_pair. Najwyraźniej to tylko dla wygody.
@Jay Tak by się wydawało.
Tor Valamo,
15
Myślę, że możesz to zrobić w one = {10, 20}dzisiejszych czasach, ale nie mam pod ręką kompilatora C ++ 11, aby to sprawdzić.
MSalters
6
Należy również pamiętać, że make_pairdziała z nienazwanymi typami, w tym strukturami, związkami, lambdami i innymi elementami dekoracyjnymi.
Mooing Duck
35

Jak @MSalters odpowiedział powyżej, możesz teraz użyć nawiasów klamrowych, aby to zrobić w C ++ 11 (właśnie zweryfikowano to za pomocą kompilatora C ++ 11):

pair<int, int> p = {1, 2};
PlagueHammer
źródło
28

Nie można wywnioskować argumentów szablonu klasy z konstruktora przed C ++ 17

Przed C ++ 17 nie można było napisać czegoś takiego:

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

ponieważ mogłoby to wywnioskować typy szablonów z argumentów konstruktora.

C ++ 17 umożliwia taką składnię, a zatem jest make_pairzbędna.

Przed C ++ 17 std::make_pairpozwalało nam pisać mniej rozwlekły kod:

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

zamiast bardziej rozwlekłych:

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

który powtarza typy i może być bardzo długi.

Wnioskowanie o typie działa w tym przypadku sprzed wersji C ++ 17, ponieważ make_pairnie jest konstruktorem.

make_pair jest zasadniczo równoważne z:

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

To samo odnosi się do koncepcji insertervs insert_iterator.

Zobacz też:

Minimalny przykład

Aby uczynić rzeczy bardziej konkretnymi, możemy zaobserwować problem w minimalnym stopniu:

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

następnie:

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

kompiluje się szczęśliwie, ale:

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

zawodzi z:

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

i zamiast tego wymaga pracy:

MyClass<int> my_class(1);

lub pomocnik:

auto my_class = make_my_class(1);

który używa zwykłej funkcji zamiast konstruktora.

Różnica dla `std :: reference_wrapper

Ten komentarz wspomina, że std::make_pairrozpakowuje się, std::reference_wrapperpodczas gdy konstruktor tego nie robi, więc to jedna różnica. Przykład TODO.

Testowane z GCC 8.1.0, Ubuntu 16.04 .

Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
źródło
1
„C ++ 17 umożliwia taką składnię, a zatem make_pair jest zbędna”. - Dlaczego std::make_pairnie stało się przestarzałe w C ++ 17?
andreee
@andreee Nie jestem pewien, możliwym powodem jest to, że nie stwarza problemów, więc nie ma potrzeby łamania starego kodu? Ale nie jestem zaznajomiony z uzasadnieniem komitetu C ++, daj mi znać, jeśli coś znajdziesz.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功
1
Jedną z przydatnych rzeczy, na które się natknąłem, jest to, że możliwość określenia typów za pomocą std :: make_pair <T1, T2> (o1, o2) zapobiega popełnieniu przez użytkownika błędu przekazywania typów o1 lub o2, które nie mogą być niejawnie rzut do T1 lub T2. Na przykład przekazanie liczby ujemnej do liczby int bez znaku. -Wsign-conversion -Werror nie wychwyci tego błędu z konstruktorem std :: pair w c ++ 11, jednak przechwyci błąd, jeśli zostanie użyte std :: make_pair.
conchoecia
make_pairrozpakowuje opakowania referencyjne, więc w rzeczywistości różni się od CTAD.
LF
26

Nie ma różnicy między użyciem make_paira jawnym wywołaniem pairkonstruktora z określonymi argumentami typu. std::make_pairjest wygodniejsze, gdy typy są rozwlekłe, ponieważ metoda szablonu ma dedukcję typu na podstawie podanych parametrów. Na przykład,

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));
diabeł
źródło
21

Warto zauważyć, że jest to powszechny idiom w programowaniu szablonów w C ++. Jest znany jako idiom Object Generator, możesz znaleźć więcej informacji i ładny przykład tutaj .

Edycja Jak ktoś zasugerował w komentarzach (od czasu usunięcia), poniżej znajduje się nieznacznie zmodyfikowany wyciąg z łącza na wypadek, gdyby się zepsuł.

Generator obiektów umożliwia tworzenie obiektów bez jawnego określania ich typów. Opiera się on na użytecznej właściwości szablonów funkcji, których szablony klas nie mają: parametry typu szablonu funkcji są wyprowadzane automatycznie z jego rzeczywistych parametrów. std::make_pairto prosty przykład, który zwraca wystąpienie std::pairszablonu w zależności od rzeczywistych parametrów std::make_pairfunkcji.

template <class T, class U>
std::pair <T, U> 
make_pair(T t, U u)
{
  return std::pair <T, U> (t,u);
}
mkm
źródło
2
@duck Właściwie &&od C ++ 11.
Justme0
5

make_pair tworzy dodatkową kopię nad konstruktorem direct. Zawsze wpisuję moje pary, aby zapewnić prostą składnię.
To pokazuje różnicę (przykład autorstwa Rampala 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
źródło
4
Jestem prawie pewien, że dodatkowa kopia zostanie usunięta we wszystkich przypadkach, jeśli ustawienia optymalizacji kompilatora są wystarczająco wysokie.
Björn Pollex
1
Dlaczego miałbyś kiedykolwiek chcieć polegać na optymalizacji kompilatora pod kątem poprawności?
sjbx
Uzyskuję te same wyniki w przypadku obu wersji i std::movetylko w środku inserti / lub w pobliżu tego, do czego byłoby odniesienie sample. Dopiero gdy zmieniam std::map<int,Sample>na std::map<int,Sample const&>to zmniejszam liczbę konstruowanych obiektów i dopiero kiedy usuwam konstruktor kopiujący, eliminuję wszystkie kopie (oczywiście). Po wprowadzeniu obu tych zmian mój wynik obejmuje jedno wywołanie domyślnego konstruktora i dwa wywołania destruktora dla tego samego obiektu. Myślę, że czegoś mi brakuje. (g ++ 5.4.1, c ++ 11)
John P
FWIW Zgadzam się, że optymalizacja i poprawność powinny być całkowicie niezależne, ponieważ dokładnie taki kod piszesz w celu sprawdzenia poprawności po tym, jak różne poziomy optymalizacji dają niespójne wyniki. Ogólnie rzecz biorąc, polecałbym emplacezamiast insertkonstruować wartość do natychmiastowego wstawienia (i nie chcesz dodatkowych instancji). To nie jest moja specjalizacja, jeśli mogę nawet powiedzieć, że ją mam, ale kopiuj / przenieś semantyka wprowadzona przez C ++ 11 bardzo mi pomogła.
John P
Wydaje mi się, że napotykam dokładnie ten sam problem i po debugowaniu przez prawie cały wieczór w końcu tu dotarłem.
lllllllllllll
1

zaczynając od c ++ 11 po prostu użyj jednolitej inicjalizacji dla par. Więc zamiast:

std::make_pair(1, 2);

lub

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

po prostu użyj

{1, 2};
Mahmoud Badri
źródło
{1, 2}może być użyty do zainicjowania pary, ale nie zatwierdza dla pary typów. Czyli przy użyciu auto trzeba zobowiązać się do określonego typu na RHS: auto p = std::pair{"Tokyo"s, 9.00};.
Markus