C++11 核心进阶:引用折叠、完美转发与可变参数模板实战


🔥草莓熊Lotso: 个人主页
❄️个人专栏: 《C++知识分享》 《Linux 入门到实践:零基础也能懂》
✨生活是默默的坚持,毅力是永久的享受!


🎬 博主简介:


文章目录

  • 前言:
  • [一. 值类别细分:左值、纯右值与将亡值](#一. 值类别细分:左值、纯右值与将亡值)
  • [二. 引用折叠:万能引用底层逻辑](#二. 引用折叠:万能引用底层逻辑)
    • [2.1 引用折叠的核心规则](#2.1 引用折叠的核心规则)
    • [2.2 万能引用的实现](#2.2 万能引用的实现)
  • [三. 完美转发:保持值类别属性的关键](#三. 完美转发:保持值类别属性的关键)
  • [四. 可变参数模板:支持任意参数的泛型编程](#四. 可变参数模板:支持任意参数的泛型编程)
    • [4.1 基本语法](#4.1 基本语法)
    • [4.2 参数包展开:编译时递归推导](#4.2 参数包展开:编译时递归推导)
  • [五. 落地实践:emplace 系列接口的实现](#五. 落地实践:emplace 系列接口的实现)
    • [5.1 emplace_back 与 push_back 的区别](#5.1 emplace_back 与 push_back 的区别)
    • [5.2 模拟实现 list 的 emplace_back](#5.2 模拟实现 list 的 emplace_back)
  • 结尾:

前言:

C++11 引入的右值引用和移动语义解决了拷贝效率问题,但要真正灵活运用,还需掌握其延伸特性 ------ 引用折叠、完美转发和可变参数模板。这三个特性是现代 C++ 泛型编程的基石,支撑了 emplace 系列接口、万能引用等高频用法,也是理解 STL 容器底层优化的关键。本文从值类别细分入手,层层拆解引用折叠的规则、完美转发的实现原理、可变参数模板的语法与应用,最后落地到emplace接口的实现,帮你吃透 C++11 泛型编程的核心逻辑。


一. 值类别细分:左值、纯右值与将亡值

C++11 以后对传统的 "左值 / 右值" 进行了更精细的划分:

  • 泛左值(glvalue):包含左值和将亡值,核心特征是 "有标识(可寻址)";
  • 纯右值(prvalue) :传统右值的核心部分,无标识、不可寻址。指那些字面值常量或者求值结果相当于字面值或是一个不具名的临时对象(匿名对象)(如字面量10、表达式x+y、临时对象string("hello"));
  • 将亡值(xvalue) :返回右值引用的函数的调用表达式和转换为右值引用的转换函数的调用表达式(如move(x)static_cast<X&&>(x));
  • 核心关系右值 = 纯右值 + 将亡值泛左值 = 左值 + 将亡值
  • 参考文档Value categories - cppreference.com
  • 关键区分
cpp 复制代码
int a = 10;
int&& b = 20;          // 20是纯右值,b是右值引用(变量本身是左值)
int&& c = std::move(a); // std::move(a)返回将亡值,c绑定该对象
string&& d = string("world"); // string("world")是纯右值,d绑定后转为将亡值

二. 引用折叠:万能引用底层逻辑

C++ 不允许直接定义 "引用的引用" 如( int&& x ),但通过模版或 typedef 间接构成时,会触发引用折叠规则,这也是"万能引用"的核心原理。

2.1 引用折叠的核心规则

  • 右值引用的右值引用折叠为右值引用,其他所有组合均折叠为左值引用

以 int 为例

原始类型组合 折叠后类型
int& & int&
int& && int&
int&& & int&
int&& && int&&
cpp 复制代码
typedef int& lref;
typedef int&& rref;

int n = 0;
lref& r1 = n; // r1 的类型是 int&
lref&& r2 = n; // r2 的类型是 int&
rref& r3 = n; // r3 的类型是 int&
rref&& r4 = 1; // r4 的类型是 int&&

实际示例

cpp 复制代码
// 由于引用折叠限定,f1实例化以后总是⼀个左值引用
template<class T>
void f1(T& x)
{
}

// 由于引用折叠限定,f2实例化后可以是左值引用,也可以是右值引用
template<class T>
void f2(T&& x)
{
}

int main()
{
	// 没有折叠->实例化为void f1(int& x)
	f1<int>(n);
	//f1<int>(0); // 报错

	// 折叠->实例化为void f1(int& x)
	f1<int&>(n);
	// f1<int&>(0); // 报错

	// 折叠->实例化为void f1(int& x)
	f1<int&&>(n);
	// f1<int&&>(0); // 报错
	
	// 折叠->实例化为void f1(const int& x)
	f1<const int&>(n);
	f1<const int&>(0);

	// 折叠->实例化为void f1(const int& x)
	f1<const int&&>(n);
	f1<const int&&>(0);

	// 没有折叠->实例化为void f2(int&& x)
	// f2<int>(n); // 报错
	f2<int>(0);

	// 折叠->实例化为void f2(int& x)
	f2<int&>(n);
	// f2<int&>(0); // 报错
	
	// 折叠->实例化为void f2(int&& x)
	// f2<int&&>(n); // 报错
	f2<int&&>(0);

	return 0;
}
  • f1我们就不用多说了,最后实例化都会是一个左值引用
  • f2 这样的函数模版中,T&& x 参数看起来是右值引用参数,但是由于引用折叠的规则,他传递左值时就是左值引用,传递右值时就是右值引用,有写地方也把这种函数模版的参数叫做万能引用。
  • Function(T&& t) 函数模版程序中,假设实参是int右值,模版参数T的推导就是 int ;实参是 int左值,模版参数T的推导就是 int&,再结合引用折叠规则,就实现了实参是左值,实例化出左值引用版本形参Function,实参是右值,实例化出右值引用版本形参的Function。
cpp 复制代码
// 万能引用-以int类型的为例
// 传左值时,推导为int&,最后就是左值引用
// 传左值时,推导为int,最后就是右值引用
template<class T>
void Function(T&& t)
{
	int a = 0;
	T x = a;
	//x++;
	cout << &a << endl;
	cout << &x << endl << endl;
}
int main()
{
	// 10是右值,推导出T为int,模板实例化为void Function(int&& t)
	Function(10); // 右值

	int a;
	// a是左值,推导出T为int&,引用折叠,模板实例化为void Function(int& t)
	Function(a); // 左值

	// std::move(a)是右值,推导出T为int,模板实例化为void Function(int&& t)
	Function(std::move(a)); // 右值

	const int b = 8;
	// a是左值,推导出T为const int&,引⽤折叠,模板实例化为void Function(const int& t)
	// 所以Function内部会编译报错,x不能++
	Function(b); // const 左值

	// std::move(b)右值,推导出T为const int,模板实例化为void Function(const int&& t)
	// 所以Function内部会编译报错,x不能++
	Function(std::move(b)); // const 右值

	return 0;
}

2.2 万能引用的实现

模板参数T&&并非单纯的右值引用,结合引用折叠后,可接收左值和右值,称为 "万能引用":

  • 当实参为左值(如int a),模板参数T推导为int&T&&折叠为int&(左值引用);
  • 当实参为右值(如10),模板参数T推导为intT&&int&&(右值引用)。

我们拿前面博客中实现过的的 List.h 来改改看,里面还涉及到完美转发,下面会讲(当然库里的push_back并没有做以下万能引用的实现,因为库里存在emplace_back()):




三. 完美转发:保持值类别属性的关键

万能引用虽然可以接受任意值类别但右值变量本身是左值,直接传递会丢失原本的类别属性。所以我们需要完美转发 std::forward,确保参数在传递过程中保持原本属性。

实际分析:

  • 上面实现过的 Function(T&& t) 函数模版程序中,传左值实例化以后是左值引用的 Function 函数,传右值实例化以后是右值引用的 Function 函数。
  • 但结合之前学过的,变量表达式都是左值属性,也就意味着一个右值被右值引用绑定后,右值引用变量表达式的属性是左值,也就是说 Function 函数中 t 的属性是左值,那么我们把 t 传递给下一层函数 Fun,那么匹配的都是左值引用版本的 Fun 函数,这里我们想要保持 t 对象的属性,就需要使用完美转发来实现。
  • 完美转发源码 :通过 static_cast<T&&> 结合引用折叠,精准还原参数的原始值类别。
cpp 复制代码
_EXPORT_STD template <class _Ty>
_NODISCARD _MSVC_INTRINSIC constexpr _Ty&& forward(remove_reference_t<_Ty>& _Arg) noexcept {
    return static_cast<_Ty&&>(_Arg);
}

_EXPORT_STD template <class _Ty>
_NODISCARD _MSVC_INTRINSIC constexpr _Ty&& forward(remove_reference_t<_Ty>&& _Arg) noexcept {
    static_assert(!is_lvalue_reference_v<_Ty>, "bad forward call");
    return static_cast<_Ty&&>(_Arg);
}
  • 完美转发 forward 本质就是一个函数模版,他主要还是通过引用折叠的方式实现,下面示例中传递给 Function 的实参是右值,T被推导为 int,没有折叠,forward 内部 t 被强制转为右值引用返回;传递给 Function 的实参是左值,T 被推导为 int& ,引用折叠为左值引用,forward 内部 t 被强转为左值引用返回。
cpp 复制代码
void Fun(int& x) { cout << "左值引用" << endl; }
void Fun(const int& x) { cout << "const 左值引用" << endl; }
void Fun(int&& x) { cout << "右指引用" << endl; }
void Fun(const int&& x) { cout << "const 右值引用" << endl; }

//万能引用
template<class T>
void Function(T&& t)
{
    Fun(forward<T>(t));
}

int main()
{
    // 10是右值,推导出T为int,模板实例化为void Function(int&& t)
    Function(10); // 右值

    int a;
    // a是左值,推导出T为int&,引用折叠,模板实例化为void Function(int& t)
    Function(a); // 左值

    // std::move(a)是右值,推导出T为int,模板实例化为void Function(int&& t)
    Function(std::move(a)); // 右值

    const int b = 8;
    // a是左值,推导出T为const int&,引用折叠,模板实例化为void Function(const int&t)
    Function(b); // const 左值

    // std::move(b)右值,推导出T为const int,模板实例化为void Function(const int&& t)
    Function(std::move(b)); // const 右值

    return 0;
}

四. 可变参数模板:支持任意参数的泛型编程

C++11 引入可变参数模板 ,允许模板参数和函数参数的个数可变,解决了 "支持任意参数个数 / 类型" 的泛型需求,是emplace系列接口、printf等函数的底层支撑。

  • C+11 支持可变参数模版,也就是说支持可变数量参数的函数模版和类模版,可变数目的参数被称为参数包,存在两种参数包:模版参数包,表示零个或多个模版参数;函数参数包:表示零个或多个函数参数
  • 我们用省略号来指出一个模版参数或函数参数的表示一个包,在模版参数列表中,class... 或 typename... 指出接下来的参数表示零或多个类型列表;在函数参数列表中,类型名后面跟 ... 指出接下来表示零个或多个形对象列表;函数参数包可以用左值引用或右值引用表示,跟前面普通模版一样,每个参数实例化时遵循引用折叠规则。

4.1 基本语法

  • 模板参数包:template<class... Args>(Args为零或多个类型);
  • 函数参数包:void Func(Args&&... args)(args为零或多个参数);
  • 包扩展:通过...触发参数包展开;
  • 参数个数获取:sizeof...(args)(返回参数包中参数的个数)。

可变参数模版的原理跟类模版类似,本质还是去实例化对应类型和个数的多个函数。

cpp 复制代码
template<class ...Args>
void Print(Args&&... args)
{
    cout << sizeof...(args) << endl;
}


// 编译器底层原理还是一样,实例化生成对应参数类型和个数的多个函数
//void Print()
//{
//    cout << 0 << endl;
//}

//void Print(int&& x)
//{
//   cout << 1 << endl;
//}

//void Print(double& d,string&& s)
//{
//   cout << 2 << endl;
//}
// ...

// 更本质去看没有可变参数模板,我们实现出这样的多个函数模板才能⽀持
// 这⾥的功能,有了可变参数模板,我们进⼀步被解放,他是类型泛化基础
// 上叠加数量变化,让我们泛型编程更灵活。
//void Print();
//template <class T1>
//void Print(T1&& arg1);
//template <class T1, class T2>
//void Print(T1&& arg1, T2&& arg2);
//template <class T1, class T2, class T3>
//void Print(T1&& arg1, T2&& arg2, T3&& arg3);
// ...

int main()
{
    double x = 2.2;
    Print(); // 包里有0个参数
    Print(1); // 包里有1个参数
    Print(x, string("xxxxx")); // 包里有2个参数
    Print(1.1,string("xxxxx"),x); // 包里有3个参数

    return 0;
}

4.2 参数包展开:编译时递归推导

  • 对于一个参数包,我们除了能计算他的参数个数,我们能做的唯一的事情就是扩展它,当扩展一个包时,我们还要提供用于每个扩展元素的模式,扩展一个包就是将它分解为构成的元素,对每个元素应用模式,获得扩展后的列表。我们通过在模式的右边放一个省略号(...)来触发扩展操作。底层的实现细节其实就是如下面的图片所示。

可变参数模板的核心是 "包扩展",通过递归调用实现参数逐个解析(需定义递归终止条件)

cpp 复制代码
// 编译时递归推导解析参数
void ShowList()
{
    // 编译器时递归的终止条件,参数包是0个时,直接匹配这个函数
    cout << endl;
}

template<class T,class ...Args>
void ShowList(T x, Args... args)
{
    cout << x << "";

    // args是N个参数的参数包
    // 调用ShowList,参数包的第一个传给x,剩下N-1传给第二个参数包
    ShowList(args...);
}

template<class ...Args>
void Print(Args ...args)
{
    // 解析参数包,递归推导
    ShowList(args...);
}

int main()
{
    Print();
    Print(1);
    Print(1,string("xxxxx"));
    Print(1,string("xxxxx"),2.2);

    return 0;
}

-- 这样其实还是很难理解的,在 C++17 中出现了折叠表达式就简单很多了。

cpp 复制代码
// 折叠表达式 C++17
template<class ...Args>
void Print(Args... args)
{
	((cout << args << " "), ...);
	cout << endl;
}

// Print(1,string("xxxxx"),2.2);
// 实例化为下面的函数
//void Print(int x, string y, double z)
//{
//    // ((cout << args << " "), ...);
//    ((cout << x << " "), (cout << y << " "), (cout << z << " "));
//    cout << endl;
//}

int main()
{
	Print();
	Print(1);
	Print(1, string("xxxxx"));
	Print(1, string("xxxxx"), 2.2);

	return 0;
}

五. 落地实践:emplace 系列接口的实现

可变参数模板 + 完美转发的典型应用是 STL 容器的emplace系列接口,其核心优势是 "直接在容器空间构造对象",避免临时对象拷贝 / 移动。

5.1 emplace_back 与 push_back 的区别

  • push_back:需先构造临时对象,再通过拷贝 / 移动构造到容器中;
  • emplace_back:接收构造对象的参数包,直接在容器节点中构造对象,减少一次拷贝 / 移动。

前置准备

cpp 复制代码
#include<iostream>
#include<assert.h>
#include<string.h>
#include<algorithm>
using namespace std;

namespace Lotso
{
	class string
	{
	public:
		typedef char* iterator;
		typedef const char* const_iterator;

		iterator begin()
		{
			return _str;
		}
		iterator end()
		{
			return _str + _size;
		}

		const_iterator begin() const
		{
			return _str;
		}

		const_iterator end() const
		{
			return _str + _size;
		}

		string(const char* str = "")
			:_size(strlen(str))
			, _capacity(_size)
		{
			cout << "string(char* str)-构造" << endl;
			_str = new char[_capacity + 1];
			strcpy(_str, str);
		}

		void swap(string& s)
		{
			::swap(_str, s._str);
			::swap(_size, s._size);
			::swap(_capacity, s._capacity);
		}

		// 拷贝构造
		string(const string& s)
		{
			cout << "string(const string& s) -- 拷贝构造" << endl;

			reserve(s._capacity);
			for (auto ch : s)
			{
				push_back(ch);
			}
		}

		// 移动构造
		string(string&& s)
		{
			cout << "string(string&& s) -- 移动构造" << endl;
			swap(s);
		}

		string& operator=(const string& s)
		{
			cout << "string& operator=(const string& s) -- 拷贝赋值" << endl;
			if (this != &s)
			{
				_str[0] = '\0';
				_size = 0;

				reserve(s._capacity);
				for (auto ch : s)
				{
					push_back(ch);
				}
			}

			return *this;
		}

		// 移动赋值
		string& operator=(string&& s)
		{
			cout << "string& operator=(string&& s) -- 移动赋值" << endl;
			swap(s);
			return *this;
		}

		~string()
		{
			//cout << "~string() -- 析构" << endl;
			delete[] _str;
			_str = nullptr;
		}

		char& operator[](size_t pos)
		{
			assert(pos < _size);
			return _str[pos];
		}

		void reserve(size_t n)
		{
			if (n > _capacity)
			{
				char* tmp = new char[n + 1];
				if (_str)
				{
					strcpy(tmp, _str);
					delete[] _str;
				}
				_str = tmp;
				_capacity = n;
			}
		}

		void push_back(char ch)
		{
			if (_size >= _capacity)
			{
				size_t newcapacity = _capacity == 0 ? 4 : _capacity * 2;
				reserve(newcapacity);
			}

			_str[_size] = ch;
			++_size;
			_str[_size] = '\0';
		}

		string& operator+=(char ch)
		{
			push_back(ch);
			return *this;
		}

		const char* c_str() const
		{
			return _str;
		}

		size_t size() const
		{
			return _size;
		}
	private:
		char* _str = nullptr;
		size_t _size = 0;
		size_t _capacity = 0;
	};
}

实际操作

  • 场景一:基础场景(string) && 减少一次移动构造
cpp 复制代码
#include<list>
// emplace_back总体而言是更高效,推荐以后使用emplace系列替代insert和push系列

int main()
{
	list<Lotso::string> lt;

	// 传左值,跟push_back一样,走拷贝构造
	Lotso::string s1("1111111111");
	lt.emplace_back(s1);
	cout << "************************************" << endl;

	// 右值,跟push_back一样,走移动构造
	lt.emplace_back(move(s1));
	cout << "************************************" << endl;

	// 直接把构造string参数包往下传,直接用string参数包构造string
	// 这里达到的效果是push_back做不到的
	lt.push_back("1111111111");
	cout << "************************************" << endl;

	lt.emplace_back("1111111111");
	cout << "************************************" << endl;

	return 0;
}
  • 场景二:进阶场景(pair,Date) && 减少一次拷贝构造
cpp 复制代码
#include<list>
struct Date
{
public:
	Date(int y,int m,int d)
		:_y(y)
		,_m(m)
		,_d(d)
	{
		cout << "Date() 构造" << endl;
	}
	Date(const Date& d)
		:_y(d._y)
		,_m(d._m)
		,_d(d._d)
	{
		cout << "Date(const Date& d) 拷贝构造" << endl;
	}

	Date(Date&& d)
		:_y(d._y)
		, _m(d._m)
		, _d(d._d)
	{
		cout << "Date(Date&& d) 移动构造" << endl;
	}
private:
	int _y = 1;
	int _m = 1;
	int _d = 1;
};

int main()
{
	list<pair<Lotso::string, int>> lt1;


	// 跟push_back一样
	// 构造 pair + 拷贝/移动构造pair到list的节点中data上
	pair<Lotso::string, int> kv("苹果",1);
	lt1.push_back(kv);
	cout << "************************************" << endl;

	// 跟push_back一样
	lt1.emplace_back(move(kv));
	cout << "************************************" << endl;

	// 直接把构造pair参数包往下传,直接用pair参数包构造pair
	// 这里达到的效果是push_back是做不到的
	lt1.emplace_back("苹果", 1);
	// lt1.push_back("苹果", 1); // 错误,要传pair或者{}隐式转换pair的值
	//lt1.push_back({ "苹果",1 });
	cout << "************************************" << endl;

	list<Date> lt;
	// 构造 + 拷贝构造
	Date d1{ 2025,11,18 };
	lt.push_back(d1);
	// lt.push_back({ 2025,11,18 });
	cout << "************************************" << endl;

	// 传构造Dtae的参数,传给形参参数包,参数包往下不断传递,最后直接构造到链表节点上
	// 直接构造
	lt.emplace_back(2025, 11, 18);

	return 0;
}

5.2 模拟实现 list 的 emplace_back

关键代码

cpp 复制代码
#pragma once

namespace Lotso
{
	template<class T>
	struct list_node
	{
		list_node<T>* _next;
		list_node<T>* _prev;
		T _data;

		list_node(T&& data)
			:_next(nullptr)
			, _prev(nullptr)
			, _data(move(data)) // 移动构造或拷贝构造
		{}

		template<class... Args>
		list_node(Args&&... args)
			: _next(nullptr)
			, _prev(nullptr)
			, _data(std::forward<Args>(args)...) 
		{}
	};

	template<class T, class Ref, class Ptr>
	struct list_iterator
	{
		using Self = list_iterator<T, Ref, Ptr>;
		using Node = list_node<T>;
		Node* _node;

		list_iterator(Node* node)
			:_node(node)
		{}

		// *it = 1
		Ref operator*() {// ......}

		Ptr operator->() {// ......}
		// ++it
		Self& operator++() {// ......}

		Self operator++(int) {// ......}

		// --it
		Self& operator--() {// ......}

		Self operator--(int) {// ......}

		bool operator!=(const Self& s) const {// ......}

		bool operator==(const Self& s) const {// ......}
	};


	template<class T>
	class list
	{
		using Node = list_node<T>;
	public:
		using iterator = list_iterator<T, T&, T*>;
		using const_iterator = list_iterator<T, const T&, const T*>;

		// using iterator = list_iterator<T>;
		// using const_iterator = list_const_iterator<T>;

		iterator begin() {// ......}

		iterator end() {// ......}

		const_iterator begin() const {// ......}
		const_iterator end() const {// ......}

		void empty_init() {// ......}

		list() {// ......}

		list(initializer_list<T> il) {// ......}

		template <class InputIterator>
		list(InputIterator first, InputIterator last) {// ......}
 
		list(size_t n, T val = T()) {// ......}

		list(int n, T val = T()) {// ......}

		~list() {// ......}

		// 传统写法
		// lt2(lt1)
		list(const list<T>& lt) {// ......}

		// lt1 = lt3;
		list<T>& operator=(const list<T>& lt) {// ......}

		void swap(list<T>& lt) {// ......}

		void clear() {// ......}

		void push_back(const T& x) {// ......}

		// 不是万能引用
		// 因为T是list的参数,list<Lotso::string>实例化时,T就确定了。
		void push_back(T&& x) {// ......}

		void push_front(const T& x) {// ......}
		void pop_back() {// ......}

		void pop_front() {// ......}

		void insert(iterator pos, const T& x) {// ......}

		void insert(iterator pos, T&& x) {// ......}

		iterator erase(iterator pos) {// ......}

		size_t size() const {// ......}

		template<class... Args>
		void emplace_back(Args&&... args)
		{
			insert(end(), std::forward<Args>(args)...);
		}

		// 原理:本质编译器根据可变参数模板生成对应参数的函数
		/*void emplace_back(string& s)
		{
			insert(end(), std::forward<string>(s));
		}
		void emplace_back(string&& s)
		{
			insert(end(), std::forward<string>(s));
		}
		void emplace_back(const char* s)
		{
			insert(end(), std::forward<const char*>(s));
		}
		*/

		template<class... Args>
		iterator insert(iterator pos,Args&&... args)
		{
			Node* cur = pos._node;
			Node* newnode = new Node(std::forward<Args>(args)...);
			Node* prev = cur->_prev;

			// prev newnode cur
			prev->_next = newnode;
			newnode->_prev = prev;
			newnode->_next = cur;

			cur->_prev = newnode;

			return iterator(newnode);
		}

	private:
		Node* _head;
		size_t _size = 0;
	};
}

完整版List.h 完整版
测试代码

cpp 复制代码
#include"List.h"
// emplace_back总体而言是更高效,推荐以后使用emplace系列替代insert和push系列
int main()
{
	Lotso::list<Lotso::string> lt;
	// 传左值,跟push_back⼀样,走拷⻉构造
	Lotso::string s1("111111111111");
	lt.emplace_back(s1);
	cout << "*********************************" << endl;

	// 右值,跟push_back⼀样,走移动构造
	lt.emplace_back(move(s1));
	cout << "*********************************" << endl;

	// 直接把构造string参数包往下传,直接用string参数包构造string
	// 这⾥达到的效果是push_back做不到的
	lt.emplace_back("111111111111");
	cout << "*********************************" << endl;

	Lotso::list<pair<Lotso::string, int>> lt1;
	// 跟push_back⼀样
	// 构造pair + 拷⻉/移动构造pair到list的节点中data上
	pair<Lotso::string, int> kv("苹果", 1);
	lt1.emplace_back(kv);
	cout << "*********************************" << endl;

	// 跟push_back⼀样
	lt1.emplace_back(move(kv));
	cout << "*********************************" << endl;

	////////////////////////////////////////////////////////////////////
	// 直接把构造pair参数包往下传,直接⽤pair参数包构造pair
	// 这⾥达到的效果是push_back做不到的
	lt1.emplace_back("苹果", 1);
	cout << "*********************************" << endl;

	return 0;
}

结尾:

html 复制代码
🍓 我是草莓熊 Lotso!若这篇技术干货帮你打通了学习中的卡点:
👀 【关注】跟我一起深耕技术领域,从基础到进阶,见证每一次成长
❤️ 【点赞】让优质内容被更多人看见,让知识传递更有力量
⭐ 【收藏】把核心知识点、实战技巧存好,需要时直接查、随时用
💬 【评论】分享你的经验或疑问(比如曾踩过的技术坑?),一起交流避坑
🗳️ 【投票】用你的选择助力社区内容方向,告诉大家哪个技术点最该重点拆解
技术之路难免有困惑,但同行的人会让前进更有方向~愿我们都能在自己专注的领域里,一步步靠近心中的技术目标!

结语:引用折叠、完美转发和可变参数模板是 C++11 泛型编程的 "三剑客",看似抽象,但落地场景十分具体 ------emplace接口、万能引用、函数参数转发等高频用法都基于这些特性。掌握它们,不仅能理解 STL 的底层优化逻辑,还能写出更灵活、高效的泛型代码。实际开发中,建议优先使用emplace_back替代push_back,合理运用万能引用和完美转发简化代码,同时注意值类别的区分,避免因转发错误导致的性能损耗或逻辑 bug。

✨把这些内容吃透超牛的!放松下吧✨ ʕ˘ᴥ˘ʔ づきらど

相关推荐
人工小情绪8 小时前
大模型核心原理-关键技术解析:预训练、SFT、RLHF
人工智能·gpt
Q_Q5110082858 小时前
小程序springBoot新农村综合风貌旅游展示平台
vue.js·spring boot·后端
风象南8 小时前
Docker 容器实现按顺序启动
后端
你不是我我8 小时前
【Java 开发日记】我们来说一下消息的可靠性投递
java·开发语言
电商API_180079052478 小时前
主流电商平台 API 横向测评:淘宝、京东、拼多多接口能力与对接成本分析
大数据·开发语言·网络·数据库·人工智能
乌萨奇也要立志学C++8 小时前
【洛谷】贪心专题之哈夫曼编码 从原理到模板题解析
c++·算法
落羽的落羽8 小时前
【C++】并查集的原理与使用
linux·服务器·c++·人工智能·深度学习·随机森林·机器学习
free-elcmacom8 小时前
Python实战项目<3>赛制分数分析
开发语言·前端·python·数据分析
赵谨言8 小时前
基于OpenCV的数字识别系统
大数据·开发语言·经验分享·python