Skip to content

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 有默认字典序比较,比较方便。

五、高频问答 ​

  1. pair 为什么只存两个值? —— C++98 时期没有可变参数模板,只能硬编码两个;C++11 有了可变参数模板才诞生了任意个数的 tuple。
  2. std::get<i>(tuple) 的 i 为什么必须是编译期常量? —— 因为 i 是模板参数,返回类型 T_i 在编译期就要确定,运行期变量无法决定返回类型。
  3. 结构化绑定能解包自定义类型吗? —— 能,只要满足:聚合体(公有非静态成员),或特化了 std::tuple_size/std::tuple_element/get。解包顺序按成员声明顺序。
  4. 结构化绑定拿到的是引用还是副本? —— 看声明:auto [a,b] 副本;auto &[a,b] 引用;const auto &[a,b] 只读引用。遍历 map 时用 const auto & 避免拷贝。
  5. 为什么现在少用 pair/tuple? —— 无语义、可读性差;struct + 结构化绑定同样简洁且自解释。这是「表达意图优先」的现代 C++ 风格。

六、自测 ​

  1. std::pair 和 std::tuple 各来自哪个标准?为什么会有时间差?
  2. 写出用结构化绑定遍历 std::map 的代码。
  3. std::get<0>(t) 里的 0 能换成运行期变量吗?为什么?
  4. 什么情况下仍应该用 pair 而不是自定义结构体?

糌粑的面经笔记 · VitePress 版