Какова цель 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
На самом деле, типы должны быть выведены во время компиляции без необходимости указывать.
Чад
@ Да, я знаю, как использовать их оба, мне было просто любопытно, была ли причина для этого std::make_pair. Видимо это просто для удобства.
@Jay Это будет выглядеть так.
Тор Валамо
15
Я думаю, что вы можете сделать в one = {10, 20}настоящее время, но у меня нет под рукой компилятора C ++ 11, чтобы проверить это.
MSalters
6
Также обратите внимание, что make_pairработает с безымянными типами, включая структуры, союзы, лямбды и другие дуды.
Mooing Duck
35

Как ответил @MSalters выше, теперь вы можете использовать фигурные скобки, чтобы сделать это в C ++ 11 (только что проверил это с помощью компилятора C ++ 11):

pair<int, int> p = {1, 2};
PlagueHammer
источник
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};

который повторяет типы и может быть очень длинным.

Вывод типа работает в этом случае до C ++ 17, потому что make_pairне является конструктором.

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 Я не уверен, возможная причина в том, что это не создает проблем, поэтому не нужно ломать старый код? Но я не знаком с обоснованием комитета C ++, пинг меня, если вы найдете что-то.
Сиро Сантилли 郝海东 冠状 病 六四 事件 法轮功
1
Одна полезная вещь, с которой я столкнулся, это то, что возможность указывать типы с помощью std :: make_pair <T1, T2> (o1, o2) предотвращает ошибку пользователя при передаче типов o1 или o2, которые не могут быть явно приведение к T1 или T2. Например, передача отрицательного числа в целое число без знака. -Wsign-преобразование -Werror не будет ловить эту ошибку с помощью конструктора std :: pair в c ++ 11, однако она будет ловить ошибку, если используется std :: make_pair.
Конхоэсия
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 создает дополнительную копию над прямым конструктором. Я всегда печатаю свои пары, чтобы обеспечить простой синтаксис.
Это показывает разницу (пример 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
источник
4
Я уверен, что дополнительная копия будет исключена во всех случаях, если настройки оптимизации компилятора достаточно высоки.
Björn Pollex
1
Зачем вам когда-либо полагаться на оптимизацию компилятора для правильности?
SJBX
Я получаю одинаковые результаты с обеими версиями, и std::moveтолько внутри insertи / или вокруг того, на что будет ссылаться sample. Только когда я изменяю std::map<int,Sample>на std::map<int,Sample const&>это, я уменьшаю количество созданных объектов, и только когда я удаляю конструктор копирования, я удаляю все копии (очевидно). После внесения обоих этих изменений мой результат включает один вызов конструктора по умолчанию и два вызова деструктора для одного и того же объекта. Я думаю, что я что-то упускаю. (g ++ 5.4.1, c ++ 11)
Джон П
Я согласен с тем, что оптимизация и корректность должны быть полностью независимыми, поскольку именно такой код вы пишете в качестве проверки работоспособности после того, как разные уровни оптимизации дают противоречивые результаты. В общем, я бы порекомендовал emplaceвместо того, insertчтобы просто создавать значение для немедленной вставки (и вам не нужны дополнительные экземпляры). Это не моя область знаний, если я могу даже сказать, что она у меня есть, а копирование / перемещение Семантика, представленная в C ++ 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};.
Маркус