Appearance
std::pair / std::tuple 与结构化绑定
面试常问:「pair 和 tuple 是什么?什么时候该用、什么时候不该用?」「结构化绑定怎么用?」 核心结论一句话:现代 C++ 里它们是「快速而粗糙」的数据结构,优先用有意义的具名类型代替。
一、std::pair(两个值)
定义在 <utility>,本质就一个简单的结构体:
cpp
template <typename T, typename U>
struct pair {
T first;
U second;
};- 来自 C++98——那时还没有可变参数模板,所以只能固定存两个值。
- 两个成员就叫
first/second(没有业务含义,这是它最大的缺点)。
cpp
std::pair<int, std::string> p(42, "hello");
std::cout << p.first << ' ' << p.second; // 42 hello
auto p2 = std::make_pair(1, 2.0); // C++17 起可省:std::pair{1, 2.0}二、std::tuple(任意个值)
定义在 <tuple>,是 pair 的推广,能存任意个不同类型的值(C++11,可变参数模板出现后才可能):
cpp
std::tuple<int, double, std::string> t(42, 3.14, "hi");
// 访问:std::get<i>
std::get<0>(t); // 42
std::get<1>(t); // 3.14
// 构造与提取
auto t2 = std::make_tuple(1, 'a', 2.0);
int a; char b;
std::tie(a, b, std::ignore) = t2; // std::tie 解包(ignore 跳过)std::get<i>(t)的i必须是编译期常量(模板参数)。- 底层实现:成员依次排列 + 空基类优化(EBCO,见 vtable.md),所以大小紧致。
三、结构化绑定(C++17,关键考点)
pair/tuple 与「结构化绑定」配合,让解包像 Python 一样简洁:
cpp
// 遍历 map:解包 pair<const Key, Value>
std::map<std::string, int> counter;
for (const auto &[word, occ] : counter) // 自动解包成 word / occ
std::cout << word << " occurred " << occ << " times.\n";
// 函数返回 pair / tuple 后直接解包
auto [x1, x2] = solve(a, b, c); // 解包 std::optional::value() 的 pair
// 解包任意具名结构体(聚合体)也行
struct Point { int x; int y; };
Point p{1, 2};
auto [px, py] = p; // px=1, py=2要点:
- 结构化绑定不限于 pair/tuple,任何可解包的类型(聚合体、有
tuple_size/get特化的类型)都行。 - 绑定的是副本还是引用取决于声明符:
auto [a,b]是副本,auto &[a,b]是引用(修改会改原对象)。
四、最佳实践:优先具名类型(面试加分点)
first/second 没有业务含义,可读性差、易错(result.second 到底是「成功标志」还是「位置」?)。L27 讲义明确建议:优先自定义具名类型 + 结构化绑定。
cpp
// ❌ 差:first/second 无语义,别人看不懂
std::pair<bool, Iterator> result = mySet.insert(x);
if (result.first)
do_something(result.second);
// ✅ 好:成员名自解释,结构化绑定后依然简洁
struct InsertResult { bool success; Iterator position; };
auto [ok, pos] = mySet.insert(x);
if (ok)
do_something(pos);判断标准:如果 first/second 的意义需要注释才能说明白,就该定义一个带名字的 struct。现代 C++ 里 pair/tuple 的正当用途已经很少了。
仍然合理的场合:
- 库接口的「通用返回」(如
std::map::insert返回pair<iterator,bool>,标准已定死)。 - 临时、局部、一眼能看懂的两三个值的打包。
- 与「排序/比较」联动时
std::pair有默认字典序比较,比较方便。
五、高频问答
- pair 为什么只存两个值? —— C++98 时期没有可变参数模板,只能硬编码两个;C++11 有了可变参数模板才诞生了任意个数的
tuple。 std::get<i>(tuple)的 i 为什么必须是编译期常量? —— 因为i是模板参数,返回类型T_i在编译期就要确定,运行期变量无法决定返回类型。- 结构化绑定能解包自定义类型吗? —— 能,只要满足:聚合体(公有非静态成员),或特化了
std::tuple_size/std::tuple_element/get。解包顺序按成员声明顺序。 - 结构化绑定拿到的是引用还是副本? —— 看声明:
auto [a,b]副本;auto &[a,b]引用;const auto &[a,b]只读引用。遍历 map 时用const auto &避免拷贝。 - 为什么现在少用 pair/tuple? —— 无语义、可读性差;
struct+ 结构化绑定同样简洁且自解释。这是「表达意图优先」的现代 C++ 风格。
六、自测
std::pair和std::tuple各来自哪个标准?为什么会有时间差?- 写出用结构化绑定遍历
std::map的代码。 std::get<0>(t)里的0能换成运行期变量吗?为什么?- 什么情况下仍应该用
pair而不是自定义结构体?