Qual é o objetivo de std :: make_pair vs o construtor de std :: pair?


Respostas:


165

A diferença é que, com std::pairvocê, é necessário especificar os tipos de ambos os elementos, ao passo std::make_pairque criará um par com o tipo dos elementos que são passados ​​para ele, sem a necessidade de informar. É o que eu pude reunir de vários documentos.

Veja este exemplo em 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>

Além do bônus implícito de conversão, se você não usasse make_pair, teria que fazer

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

toda vez que você atribuiu a um, o que seria irritante ao longo do tempo ...


1
Na verdade, os tipos devem ser deduzidos no tempo de compilação sem a necessidade de especificar.
Chad

@ Tor Sim, eu sei como usar os dois, eu só estava curioso se havia uma razão para std::make_pair. Aparentemente, é apenas por conveniência.

@ Jay Parece que sim.
Tor Valamo

15
Eu acho que você pode fazer one = {10, 20}hoje em dia, mas não tenho um compilador C ++ 11 à mão para verificá-lo.
MSalters 14/02

6
Observe também que make_pairfunciona com tipos sem nome, incluindo estruturas, uniões, lambdas e outros doodads.
Mooing Duck

35

Como o @MSalters respondeu acima, agora você pode usar chaves para fazer isso no C ++ 11 (verifique isso apenas com um compilador C ++ 11):

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

28

Argumentos de modelo de classe não puderam ser deduzidos do construtor antes do C ++ 17

Antes do C ++ 17, você não podia escrever algo como:

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

pois isso inferiria tipos de modelo a partir dos argumentos do construtor.

O C ++ 17 torna essa sintaxe possível e, portanto, make_pair redundante.

Antes do C ++ 17, std::make_pairnos permitia escrever um código menos detalhado:

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

em vez do mais detalhado:

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

que repete os tipos e pode ser muito longo.

A inferência de tipo funciona nesse caso anterior ao C ++ 17 porque make_pairnão é um construtor.

make_pair é essencialmente 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);
}

O mesmo conceito se aplica a insertervs insert_iterator.

Veja também:

Exemplo mínimo

Para tornar as coisas mais concretas, podemos observar o problema minimamente com:

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

então:

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

compila alegremente, mas:

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

falha com:

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

e requer, em vez disso, trabalhar:

MyClass<int> my_class(1);

ou o ajudante:

auto my_class = make_my_class(1);

que usa uma função regular em vez de um construtor.

Diferença para `std :: reference_wrapper

Este comentário menciona que std::make_pairdesembrulhastd::reference_wrapper enquanto o construtor não, então essa é uma diferença. Exemplo TODO.

Testado com o GCC 8.1.0, Ubuntu 16.04 .


1
"O C ++ 17 torna essa sintaxe possível e, portanto, torna o par redundante." - Por que std::make_pairnão foi preterido no C ++ 17?
andreee

@andreee Não tenho certeza, o possível motivo é que ele não cria problemas, portanto não há necessidade de quebrar o código antigo? Mas eu não estou familiarizado com o raciocínio do comitê C ++, faça ping se encontrar alguma coisa.
Ciro Santilli #

1
Uma coisa útil que me deparei é que a capacidade de especificar os tipos com std :: make_pair <T1, T2> (o1, o2) impede que o usuário cometa o erro de passar os tipos o1 ou o2 que não podem ser implicitamente convertido para T1 ou T2. Por exemplo, passando um número negativo para um int não assinado. -Wsign-conversion -Werror não detectará esse erro com o construtor std :: pair no c ++ 11, mas detectará o erro se std :: make_pair for usado.
conchoecia

make_pairdesembrulha invólucros de referência, por isso é diferente do CTAD.
LF

26

Não há diferença entre usar make_paire chamar explicitamente o pairconstrutor com argumentos de tipo especificado. std::make_pairé mais conveniente quando os tipos são detalhados porque um método de modelo possui dedução de tipo com base em seus parâmetros fornecidos. Por exemplo,

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 a pena notar que esse é um idioma comum na programação de modelos C ++. É conhecido como o idioma do Gerador de Objetos, você pode encontrar mais informações e um bom exemplo aqui .

Editar Como alguém sugeriu nos comentários (desde que removido), a seguir é um extrato ligeiramente modificado do link, caso ele se quebre.

Um gerador de objetos permite a criação de objetos sem especificar explicitamente seus tipos. Ele se baseia em uma propriedade útil dos modelos de função que os modelos de classe não possuem: Os parâmetros de tipo de um modelo de função são deduzidos automaticamente de seus parâmetros reais. std::make_pairé um exemplo simples que retorna uma instância do std::pairmodelo, dependendo dos parâmetros reais da std::make_pairfunção.

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

2
@duck Na verdade, &&desde C ++ 11.
usar o seguinte código

5

make_pair cria uma cópia extra sobre o construtor direto. Eu sempre digitava meus pares para fornecer uma sintaxe simples.
Isso mostra a diferença (exemplo de 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
Tenho certeza de que a cópia extra será elidida em todos os casos, se as configurações de otimização do compilador forem altas o suficiente.
Björn Pollex

1
Por que você gostaria de confiar nas otimizações do compilador para correção?
Sjbx 02/12/16

Eu obtenho os mesmos resultados com as duas versões e std::moveapenas dentro inserte / ou em torno do que seria uma referência sample. Somente quando mudo std::map<int,Sample>para std::map<int,Sample const&>isso reduzo o número de objetos construídos e somente quando excluo o construtor de cópias é que elimino todas as cópias (obviamente). Depois de fazer essas duas alterações, meu resultado inclui uma chamada para o construtor padrão e duas chamadas para o destruidor para o mesmo objeto. Eu acho que devo estar faltando alguma coisa. (g ++ 5.4.1, c ++ 11)
João P

FWIW Concordo que a otimização e a correção devem ser completamente independentes, pois esse é exatamente o tipo de código que você escreve como verificação de integridade após diferentes níveis de otimização produzirem resultados inconsistentes. Em geral, eu recomendaria, em emplacevez de, insertse você está construindo um valor para inserir imediatamente (e não deseja instâncias extras). Não é minha área de especialização, se posso dizer que tenho uma, mas copiar / mover a semântica introduzida pelo C ++ 11 me ajudou muito.
John P

Acredito que estou encontrando exatamente o mesmo problema e, depois de depurar a noite inteira, finalmente cheguei aqui.
lllllllllllll

1

a partir do c ++ 11, basta usar a inicialização uniforme para pares. Então, em vez de:

std::make_pair(1, 2);

ou

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

Apenas use

{1, 2};

{1, 2}pode ser usado para inicializar um par, mas não confirma para o par de tipos. Ou seja, ao usar auto você tem de se comprometer com um tipo no RHS: auto p = std::pair{"Tokyo"s, 9.00};.
Markus
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.