std::make_pair和std::pair的构造函数有什么不同?它们各自的作用是什么?

244

std::make_pair的目的是创建一个 std::pair 对象。

为什么不能直接使用std::pair<int, char>(0, 'a')

这两种方法之间有什么区别吗?


7
在C++11中,您几乎可以完全不使用make_pair。请参见我的答案 - Debajit
6
在C++17中,std::make_pair是多余的。下面有一个详细解释。 - Drew Dormann
2
简而言之:只需使用花括号。;) { 0,'a' }(任何有一定时间编写JavaScript代码的人都会特别喜欢这个)。 - Andrew
2
std::make_pair(vec.cbegin(), vec.cend())std::pair<std::vector<std::string>::const_iterator, std::vector<std::string>::const_iterator>(vec.cbegin(), vec.cend()) 有何区别? - Mooing Duck
8个回答

211

(由于CTAD,此答案仅适用于C++14及更早版本)

区别在于使用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)

每次你分配一个,随着时间的推移这会变得很烦人...

2
实际上,类型应该在编译时推断,无需指定。 - Chad
@Tor 是的,我知道如何使用它们两个,只是好奇为什么要使用 std::make_pair。显然这只是为了方便。 - user542687
26
我认为现在可以使用 one = {10, 20} 这种语法,但我手头没有C++11编译器来进行检查。 - MSalters
7
请注意,make_pair 可以用于无名类型,包括结构体、联合体、lambda 表达式和其他类型。 - Mooing Duck
区别在于...你需要指定两个元素的类型。这个答案仅适用于C++14及之前版本。 - Drew Dormann

58

在C++17之前,无法从构造函数中推断出类模板参数

在C++17之前,您无法编写以下内容:

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

由于这将从构造函数参数中推断模板类型,因此您需要明确编写它:

std::pair<int,char> 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);
}

相同的概念也适用于 inserterinsert_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下测试通过。

3
C++17 使得该语法成为可能,因此 make_pair 变得多余。但是为什么在 C++17 中std::make_pair 没有被弃用呢? - andreee
3
@andreee 我不确定,可能的原因是它不会引起问题,所以没有必要破坏旧代码?但我不熟悉C++委员会的理由,请在找到任何信息时与我联系。 - Ciro Santilli OurBigBook.com
3
我遇到的一个有用的事情是,能够使用std::make_pair<T1, T2>(o1, o2)指定类型可以防止用户犯错,即传递不能隐式转换为T1或T2的类型o1或o2。例如,将负数传递给无符号整数。在c++11中,-Wsign-conversion -Werror不会捕获std::pair构造函数中的此错误,但如果使用std::make_pair,则会捕获该错误。 - conchoecia
1
make_pair 解开引用包装器,因此它与 CTAD 有所不同。 - L. F.

53

如@MSalters在上面回答的那样,在C++11中现在可以使用花括号来实现这一点(我刚刚使用C++11编译器验证了这一点):

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

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

更简短的写法:vecOfPair.emplace_back(emptyV, emptyV); - DarioP

21
值得注意的是,这是C++模板编程中常见的习惯用语。它被称为“对象生成器习惯用语”,您可以在此处找到更多信息和一个很好的示例。 编辑:正如评论中(已删除)有人建议的那样,以下是从链接中略微修改的摘录,以防它出现故障。
对象生成器允许创建对象而不需要明确指定它们的类型。它基于函数模板的一个有用属性,而类模板则没有:函数模板的类型参数会自动从其实际参数中推导出来。std::make_pair 就是一个简单的例子,根据 std::make_pair 函数的实际参数返回 std::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相比于直接构造函数会创建额外的副本。我总是使用typedef为我的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;
}

4
如果编译器的优化设置足够高,我相信额外的副本在所有情况下都会被省略。 - Björn Pollex
1
你为什么要依赖编译器优化来保证正确性呢? - sjbx
我使用两个版本和std::moveinsert内部以及/或者在对sample的引用周围得到了相同的结果。只有当我将std::map<int,Sample>更改为std::map<int,Sample const&>时,才会减少构造对象的数量,并且只有在删除复制构造函数时才能消除所有副本(显然)。在进行这两个更改后,我的结果包括一个默认构造函数调用和两个相同对象的析构函数调用。我认为我可能漏掉了什么。(g++ 5.4.1,c++11) - John P
就我个人而言,我同意优化和正确性应该完全独立,因为这正是在不同的优化级别产生不一致结果后编写的代码类型,以进行健全性检查。一般来说,如果您只是要立即构造一个要插入的值(并且您不想要额外的实例),我建议使用emplace而不是insert。虽然这不是我的专业领域,如果我可以这么说,但C++11引入的复制/移动语义对我帮助很大。 - John P
我相信我遇到了完全相同的问题,在整个晚上进行了调试后,我终于来到了这里。 - lllllllllllll

2
从 C++11 开始,只需使用统一初始化语法即可对 pair 进行初始化。因此,不再需要:
std::make_pair(1, 2);

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

只需使用

{1, 2};

3
{1, 2} 可用于初始化一个pair,但不会确定pair的类型。即当使用auto时,您必须在右侧指定pair的类型: auto p = std::pair{"Tokyo"s, 9.00}; - Markus

0
我再也不会使用std::make_pair了,因为它在我的观点下是“有问题的”(至少对我来说不直观)。考虑以下示例:
你有一个支持存储,或者一个字符串池。
using Id = int;
std::vector<std::string> string_pool;
std::map<std::string_view, Id> str_to_id_map;

如果你尝试这样插入到 std::map 中;
str_to_id_map.insert(std::make_pair(string_pool.back(), 42));

这将是一个惨败。它首先会创建一个std::string的副本,然后将其转换为std::string_view - 现在,这个string_view指向"任何地方"。它可能有效,也可能无效。我最近测试过。
然而,如果你改用
str_to_id_map.insert({string_pool.back(), 42});

这将被编译器神明正确地推断为对字符串池内容的string_view,并且将正常工作 - 没有中间副本的std::string,转换为string_view。
我从中得到的教训或体会是 - 永远不要使用std::make_pair。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接