std::make_pair()为何使用右值引用作为形参类型?
关于
make_pair使用T&&形参的核心说明 首先纠正一个常见认知误区:这个函数签名里的t1&&、t2&&不是普通的右值引用,而是C++11引入的转发引用(也叫万能引用),它天生就同时支持左值、右值传入,根本不存在“无法接收左值”的问题。
为什么它能同时支持左值、右值传入?
这是模板参数推导+引用折叠规则共同作用的结果:
- 当传入左值时,模板会把
t1/t2推导为对应类型的左值引用。比如传入int类型左值,t1会被推导为int&,经过引用折叠规则处理后,int& &&会坍缩为int&(左值引用形参),可以正常绑定左值。 - 当传入右值时,模板会把
t1/t2推导为原始值类型。比如传入int类型右值,t1会被推导为int,形参类型就是int&&(右值引用形参),可以正常绑定右值。
你可以直接写代码验证这个特性:
int a = 10; auto p = std::make_pair(a, 20); // 传入左值a,完全可以正常编译运行
这里使用转发引用形参的实际作用
核心目的是配合std::forward实现完美转发,在不丢失参数值类别的前提下把参数传给pair的构造函数,把运行性能拉到最优,对比其他写法的劣势就能直观看出优势:
- 对比C++11之前的
const T&形参版本
老版本make_pair的签名是pair<T1,T2> make_pair(const T1& x, const T2& y),它只能通过const左值引用绑定对象,构造pair内部元素时只能走拷贝构造,哪怕传入的是可以直接转移资源的临时对象,也没法触发移动语义,会产生无意义的拷贝开销。 - 对比值传递形参版本
如果写成make_pair(T1 x, T2 y),不管传左值还是右值,形参本身都要先完成一次拷贝/移动构造,多了一层不必要的开销,尤其是传入大尺寸对象左值时,拷贝成本非常高。 - 转发引用版本的实际收益
它可以完整保留传入参数的值类别:传入左值时,最终调用拷贝构造初始化pair内的元素,符合左值拷贝的语义;传入右值(临时对象、std::move转换后的对象)时,最终调用移动构造初始化pair内的元素,直接转移对象内部的堆资源,完全没有额外拷贝开销。
举个直观的性能对比例子:
std::string large_str(100000, 'a'); // 一个占用10w字节堆内存的大字符串 // 传左值large_str:走拷贝构造,pair内的字符串复制一份原内容,原字符串保留不变 auto p1 = std::make_pair(large_str, 1); // 传std::move(large_str):走移动构造,pair内的字符串直接拿走原字符串的堆内存指针,原字符串变为空,无拷贝开销 auto p2 = std::make_pair(std::move(large_str), 2); // 传临时字符串对象:直接移动构造,无拷贝开销 auto p3 = std::make_pair(std::string(100000, 'b'), 3);
一个容易踩的判断误区
不要看到&&就判定为右值引用:
- 如果
&&前面是确定的非推导类型,比如void func(int&& x)、void func(std::string&& s),这才是普通右值引用,只能绑定右值,不能接收左值。 - 如果
&&前面是需要靠传入参数推导的模板类型,比如make_pair里的t1&&,这就是转发引用,可以绑定左值、右值、不同const属性的所有对象。
内容的提问来源于stack exchange,提问作者mayank mahajan
相关产品推荐
相关产品推荐

