Яка мета std :: make_pair проти конструктора std :: pair?


180

Яка мета std::make_pair?

Чому б просто не зробити std::pair<int, char>(0, 'a')?

Чи є різниця між двома методами?


6
У C ++ 11 ви можете майже повністю обійтися без make_pair. Дивіться мою відповідь .
PlagueHammer

2
В C ++ 17 std::make_pairє зайвим. Нижче є відповідь, яка детально пояснює це.
Дрю Дорман

Відповіді:


165

Різниця полягає в тому, що std::pairвам потрібно вказати типи обох елементів, тоді як std::make_pairстворіть пару з типом елементів, які передаються йому, без того, щоб вам це казали. Ось що я могла зібрати з різних документів будь-коли.

Дивіться цей приклад з 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>

Окрім неявного бонусу за конверсію, якщо ви не використовували make_pair, вам доведеться це робити

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

кожного разу, коли вас призначили одному, що з часом буде дратувати ...


1
Насправді типи слід виводити під час компіляції без необхідності вказувати.
Чад

@Tor Так, я знаю, як використовувати їх обох, мені було просто цікаво, якщо є причина std::make_pair. Мабуть, це просто для зручності.

@Jay Здавалося б, так.
Tor Valamo

15
Я думаю, що ви можете це зробити one = {10, 20}сьогодні, але у мене немає компілятора C ++ 11, щоб перевірити це.
MSalters

6
Також зауважте, що make_pairпрацює з неназваними типами, включаючи структи, союзи, лямбдаси та інші каракулі.
Mooing Duck

35

Як @MSalters відповів вище, тепер ви можете використовувати фігурні дужки, щоб зробити це в C ++ 11 (просто підтвердили це компілятором C ++ 11):

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

28

Аргументи шаблону класу не можна було вивести з конструктора до C ++ 17

Перед C ++ 17 ви не могли написати щось на кшталт:

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

оскільки це виводить типи шаблонів з аргументів конструктора.

C ++ 17 робить цей синтаксис можливим і, отже, make_pairзайвим.

Перед C ++ 17 std::make_pairдозволяв нам писати менше багатослівного коду:

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

замість більш багатослівного:

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

що повторює типи, і може бути дуже довгим.

Тип умовиводу працює в тому випадку, що make_pairне дорівнює C ++ 17, оскільки не є конструктором.

make_pair по суті еквівалентний:

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

Те ж саме відноситься і до inserterпорівнянні insert_iterator.

Дивитися також:

Мінімальний приклад

Щоб зробити конкретніші речі, ми можемо спостерігати за проблемою мінімально за допомогою:

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

тоді:

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

компілює радісно, ​​але:

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

не вдається:

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

і вимагає замість цього працювати:

MyClass<int> my_class(1);

або помічник:

auto my_class = make_my_class(1);

який використовує регулярну функцію замість конструктора.

Різниця для `std :: reference_wrapper

У цьому коментарі згадується, що std::make_pairрозгортається, std::reference_wrapperпоки конструктор цього не робить, тому це одна різниця. Приклад TODO

Тестовано на GCC 8.1.0, Ubuntu 16.04 .


1
"C ++ 17 робить цей синтаксис можливим, а тому make_pair зайвим." - Чому std::make_pairв C ++ 17 не старіло?
andreee

@andreee Я не впевнений, можлива причина в тому, що це не створює проблем, тому не потрібно ламати старий код? Але я не знайомий з обґрунтуванням комітету C ++, пінг мене, якщо ви щось знайдете.
Ciro Santilli 郝海东 冠状 病 六四 事件 法轮功

1
Одне корисне, що я натрапив - це те, що можливість вказувати типи за допомогою std :: make_pair <T1, T2> (o1, o2) заважає користувачеві помилитися з передачею типів o1 або o2, які не можуть бути неявними кинутий на T1 або T2. Наприклад, передача негативного числа неподписаному int. -Wsign-conversion -Werror не буде вловлювати цю помилку за допомогою конструктора std :: pair в c ++ 11, однак вона вбере помилку, якщо буде використано std :: make_pair.
conchoecia

make_pairрозгортає обгортки для посилань, тому вона насправді відрізняється від CTAD.
LF

26

Немає різниці між використанням make_pairта явним викликом pairконструктора із заданими аргументами типу. std::make_pairзручніше, коли типи є багатослівними, оскільки метод шаблона має виведення типу на основі заданих параметрів. Наприклад,

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

Варто зазначити, що це загальна ідіома в програмуванні шаблонів на C ++. Він відомий як Object Generator ідіоми, ви можете знайти більш детальну інформацію та хороший приклад тут .

Редагувати Як хтось запропонував у коментарях (з моменту видалення), наступне - це дещо змінений витяг із посилання на випадок, коли воно перерветься.

Генератор об'єктів дозволяє створювати об'єкти без явного зазначення їх типів. Він заснований на корисному властивості шаблонів функцій, у яких шаблонів класів немає: Параметри типу шаблону функції виводяться автоматично з його фактичних параметрів. std::make_pair- простий приклад, який повертає екземпляр std::pairшаблону залежно від фактичних параметрів std::make_pairфункції.

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

2
@duck Насправді &&з C ++ 11.
Justme0

5

make_pair створює додаткову копію над прямим конструктором. Я завжди набираю свої пари, щоб надати простий синтаксис.
Це показує різницю (приклад Рампала Чадхарі):

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
Я майже впевнений, що додаткова копія буде залишена у всіх випадках, якщо налаштування оптимізації компілятора досить високі.
Бьорн Поллекс

1
Чому ви коли-небудь хочете покластися на оптимізацію компілятора для правильності?
sjbx

Я отримую однакові результати як в обох версіях, так і в std::moveпросторі insertта / або навколо того, на що було б посилання sample. Лише коли я переходжу std::map<int,Sample>до std::map<int,Sample const&>цього, я зменшую кількість побудованих об'єктів, і лише коли я видаляю конструктор копій, я усуваю всі копії (очевидно). Після внесення обох цих змін мій результат включає в себе один виклик конструктору за замовчуванням і два виклики деструктора для того ж об’єкта. Я думаю, що мені щось треба бракувати. (г ++ 5.4.1, c ++ 11)
Іоанн П

FWIW Я погоджуюся, що оптимізація та коректність повинні бути повністю незалежними, оскільки це саме той тип коду, який ви пишете як перевірку правильності після того, як різні рівні оптимізації дають непослідовні результати. Взагалі я б рекомендував emplaceзамість того, insertякщо ви просто будуєте значення, яке потрібно вставити негайно (і ви не хочете зайвих примірників.) Це не моя область знань, якщо я можу навіть сказати, що у мене є, але копія / переміщення семантика, введена С ++ 11, мені дуже допомогла.
Джон П

Я вважаю, що я стикаюся з точно такою ж проблемою, і після налагодження протягом усього вечора я нарешті прийшов сюди.
lllllllllllll

1

починаючи з c ++ 11 просто використовуйте рівномірну ініціалізацію для пар. Тож замість:

std::make_pair(1, 2);

або

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

просто використовувати

{1, 2};

{1, 2}може бути використаний для ініціалізації пари, але не вказує на тип типу. Тобто при використанні авто ви повинні взяти на себе зобов'язання типу на РІТ: auto p = std::pair{"Tokyo"s, 9.00};.
Маркус
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.