如何在使用 optional 返回 unordered_map 查询结果的时候不触发数据拷贝
先看下最初的代码实现:
C++
#include <iostream>
#include <unordered_map>
#include <optional>
#include <cstdint>
#include <string>
// -------------------- Key 结构体 --------------------
struct Key {
uint16_t part1;
uint16_t part2;
bool operator==(const Key &other) const noexcept {
return part1 == other.part1 && part2 == other.part2;
}
};
// 为 Key 提供哈希函数
struct KeyHash {
std::size_t operator()(const Key &k) const noexcept {
return (static_cast<std::size_t>(k.part1) << 16) ^ k.part2;
}
};
// -------------------- Value 结构体 --------------------
enum class Status: uint8_t {
OK,
Warning,
Error
};
struct Detail {
int count;
std::string description;
};
struct Value {
bool flag;
Status status;
Detail detail;
// 日志辅助:追踪拷贝/移动
Value(bool f, Status s, Detail d) : flag(f), status(s), detail(std::move(d)) {
std::cout << "Value constructed\n";
}
Value(const Value &other) : flag(other.flag), status(other.status), detail(other.detail) {
std::cout << "Value copied\n";
}
Value(Value &&other) noexcept
: flag(other.flag), status(other.status), detail(std::move(other.detail)) {
std::cout << "Value moved\n";
}
};
// -------------------- 全局 unordered_map,带初始化 --------------------
std::unordered_map<Key, Value, KeyHash> globalMap = {
{{1, 100}, {true, Status::OK, {42, "All good"}}},
{{2, 200}, {false, Status::Warning, {7, "Check input"}}},
{{3, 300}, {true, Status::Error, {0, "Failure"}}}
};
// -------------------- 查找函数 --------------------
std::optional<Value> findValue(const Key &key) {
auto it = globalMap.find(key);
if (it != globalMap.end()) {
return it->second; // 会发生一次拷贝
}
return std::nullopt;
}
// -------------------- 示例使用 --------------------
int main() {
std::cout << "--- 查找存在的 key ---\n";
if (auto result = findValue({2, 200})) {
std::cout << "Found! flag=" << result->flag
<< " description=" << result->detail.description << "\n";
} else {
std::cout << "Not found!\n";
}
std::cout << "--- 查找不存在的 key ---\n";
if (auto result = findValue({9, 900})) {
std::cout << "Found!\n";
} else {
std::cout << "Not found!\n";
}
return 0;
}
findValue
的功能是在 globalMap
这个 std::unordered_map<Key, Value, KeyHash>
中根据 Key
查找对应的 Value
,无论找到与否都包装到 optional
中返回。其中 Key
和 Value
都是结构体,且 Value
尤其复杂,还嵌套了其他结构体。
上面代码编译运行后控制台输出如下:
lua
Value constructed
Value copied
Value constructed
Value copied
Value constructed
Value copied
Value copied
Value copied
Value copied
--- 查找存在的 key ---
Value copied
Found! flag=0 description=Check input
--- 查找不存在的 key ---
Not found!
从"--- 查找存在的 key ---"开始是 main
函数的执行输出。可以看到在"查找存在的 key"这个场景下 findValue
在返回结果时发生了一次拷贝。
解决办法是使用 std::reference_wrapper
,将 findValue
函数从返回 std::optional<Value>
改成返回 std::optional<std::reference_wrapper<const Value> >
。但要注意一点:返回的引用有效期取决于 globalMap
,调用者不能存储这个引用超过 globalMap
生命周期。
修改后的代码如下:
C++
#include <iostream>
#include <unordered_map>
#include <optional>
#include <cstdint>
#include <string>
// -------------------- Key 结构体 --------------------
struct Key {
uint16_t part1;
uint16_t part2;
bool operator==(const Key &other) const noexcept {
return part1 == other.part1 && part2 == other.part2;
}
};
// 为 Key 提供哈希函数
struct KeyHash {
std::size_t operator()(const Key &k) const noexcept {
return (static_cast<std::size_t>(k.part1) << 16) ^ k.part2;
}
};
// -------------------- Value 结构体 --------------------
enum class Status: uint8_t {
OK,
Warning,
Error
};
struct Detail {
int count;
std::string description;
};
struct Value {
bool flag;
Status status;
Detail detail;
// 日志辅助:追踪拷贝/移动
Value(bool f, Status s, Detail d) : flag(f), status(s), detail(std::move(d)) {
std::cout << "Value constructed\n";
}
Value(const Value &other) : flag(other.flag), status(other.status), detail(other.detail) {
std::cout << "Value copied\n";
}
Value(Value &&other) noexcept
: flag(other.flag), status(other.status), detail(std::move(other.detail)) {
std::cout << "Value moved\n";
}
};
// -------------------- 全局 unordered_map,带初始化 --------------------
std::unordered_map<Key, Value, KeyHash> globalMap = {
{{1, 100}, {true, Status::OK, {42, "All good"}}},
{{2, 200}, {false, Status::Warning, {7, "Check input"}}},
{{3, 300}, {true, Status::Error, {0, "Failure"}}}
};
// -------------------- 查找函数(避免复制) --------------------
std::optional<std::reference_wrapper<const Value> > findValue(const Key &key) {
auto it = globalMap.find(key);
if (it != globalMap.end()) {
return std::cref(it->second); // 返回引用包装器
}
return std::nullopt;
}
// -------------------- 示例使用 --------------------
int main() {
std::cout << "--- 查找存在的 key ---\n";
if (auto result = findValue({2, 200})) {
// 注意 result 是 optional<reference_wrapper<const Value>>
const Value &v = result->get();
std::cout << "Found! flag=" << v.flag
<< " description=" << v.detail.description << "\n";
} else {
std::cout << "Not found!\n";
}
std::cout << "--- 查找不存在的 key ---\n";
if (auto result = findValue({9, 900})) {
std::cout << "Found!\n";
} else {
std::cout << "Not found!\n";
}
return 0;
}
编译运行后控制台输出如下:
lua
Value constructed
Value copied
Value constructed
Value copied
Value constructed
Value copied
Value copied
Value copied
Value copied
--- 查找存在的 key ---
Found! flag=0 description=Check input
--- 查找不存在的 key ---
Not found!
可以看到,这样实现后,findValue
函数执行过程中不会发生任何对 Value
的数据复制或移动。
性能对比:使用与不使用 reference_wrapper
下面对比修改前后的性能,分成查找存在的 key 和查找不存在的 key 两种测试场景。globalMap
中键值对的数量为 1024
,两种测试场景均分别调用修改前后的 findValue
函数查询 10240000
次。为了让性能差异更显著,在 Detail
结构体中新增了 std::vector<double> numbers
字段,从而让 Value
变得更大。测试过程中的 checksum 计算是为了确保查询动作不会被编译器优化掉。
C++
#include <iostream>
#include <unordered_map>
#include <optional>
#include <cstdint>
#include <string>
#include <functional>
#include <chrono>
#include <vector>
#include <random>
#include <cassert>
// -------------------- Key 结构体 --------------------
struct Key {
uint16_t part1;
uint16_t part2;
bool operator==(const Key &other) const noexcept {
return part1 == other.part1 && part2 == other.part2;
}
};
// 为 Key 提供哈希函数
struct KeyHash {
std::size_t operator()(const Key &k) const noexcept {
return (static_cast<std::size_t>(k.part1) << 16) ^ k.part2;
}
};
// -------------------- Value 结构体 --------------------
enum class Status: uint8_t {
OK,
Warning,
Error
};
struct Detail {
int count;
std::string description;
std::vector<double> numbers; // 新增字段,为了演示在Value结构体比较大时的性能差异
};
struct Value {
bool flag;
Status status;
Detail detail;
};
// -------------------- 全局 map --------------------
std::unordered_map<Key, Value, KeyHash> globalMap;
// -------------------- 版本 A:返回 optional<Value> --------------------
std::optional<Value> findValueA(const Key &key) {
auto it = globalMap.find(key);
if (it != globalMap.end()) {
return it->second; // 发生复制
}
return std::nullopt;
}
// -------------------- 版本 B:返回 optional<reference_wrapper<const Value>> --------------------
std::optional<std::reference_wrapper<const Value> > findValueB(const Key &key) {
auto it = globalMap.find(key);
if (it != globalMap.end()) {
return std::cref(it->second); // 避免复制
}
return std::nullopt;
}
// -------------------- 性能测试 --------------------
int main() {
constexpr int EXIST_N = 1024; // 存在 key 数量
constexpr int NONEXIST_N = 1024; // 不存在 key 数量
constexpr int REPEAT = 10000; // 每个集合重复查找次数(放大测试量)
std::mt19937 rng(42);
std::uniform_real_distribution<double> distReal(0.0, 1.0);
std::vector<Key> existKeys;
std::vector<Key> nonexistKeys;
// 生成存在的 key 集合
for (int i = 0; i < EXIST_N; ++i) {
existKeys.push_back({static_cast<uint16_t>((i >> 5) & 0xFFFF), static_cast<uint16_t>(i & 0xFFFF)});
}
// 插入到 globalMap(每个 Detail 带有 200 个随机 double)
for (int i = 0; i < EXIST_N; ++i) {
Detail d{i, "data_" + std::to_string(i)};
for (int j = 0; j < 200; ++j) {
d.numbers.push_back(distReal(rng));
}
globalMap[existKeys[i]] = Value{(i % 2 == 0), Status::OK, std::move(d)};
}
// 生成不存在的 key 集合(与存在 key 偏移,确保无交集)
for (int i = 0; i < NONEXIST_N; ++i) {
nonexistKeys.push_back({
static_cast<uint16_t>(((i + EXIST_N + 1000) >> 5) & 0xFFFF),
static_cast<uint16_t>((i + EXIST_N + 1000) & 0xFFFF)
});
}
// 确保两个 key 集合没有交集
for (auto &k: nonexistKeys) {
assert(globalMap.find(k) == globalMap.end());
}
// -------------------- 测试存在 key (A) --------------------
auto startA_exist = std::chrono::high_resolution_clock::now();
double checksumA_exist = 0;
for (int r = 0; r < REPEAT; ++r) {
for (auto &k: existKeys) {
auto res = findValueA(k);
if (res) {
checksumA_exist += res->detail.numbers[0];
}
}
}
auto endA_exist = std::chrono::high_resolution_clock::now();
auto durA_exist = std::chrono::duration_cast<std::chrono::milliseconds>(endA_exist - startA_exist).count();
// -------------------- 测试存在 key (B) --------------------
auto startB_exist = std::chrono::high_resolution_clock::now();
double checksumB_exist = 0;
for (int r = 0; r < REPEAT; ++r) {
for (auto &k: existKeys) {
auto res = findValueB(k);
if (res) {
checksumB_exist += res->get().detail.numbers[0];
}
}
}
auto endB_exist = std::chrono::high_resolution_clock::now();
auto durB_exist = std::chrono::duration_cast<std::chrono::milliseconds>(endB_exist - startB_exist).count();
// -------------------- 测试不存在 key (A) --------------------
auto startA_non = std::chrono::high_resolution_clock::now();
double checksumA_non = 0;
for (int r = 0; r < REPEAT; ++r) {
for (auto &k: nonexistKeys) {
auto res = findValueA(k);
if (res) {
checksumA_non += res->detail.numbers[0];
}
}
}
auto endA_non = std::chrono::high_resolution_clock::now();
auto durA_non = std::chrono::duration_cast<std::chrono::milliseconds>(endA_non - startA_non).count();
// -------------------- 测试不存在 key (B) --------------------
auto startB_non = std::chrono::high_resolution_clock::now();
double checksumB_non = 0;
for (int r = 0; r < REPEAT; ++r) {
for (auto &k: nonexistKeys) {
auto res = findValueB(k);
if (res) {
checksumB_non += res->get().detail.numbers[0];
}
}
}
auto endB_non = std::chrono::high_resolution_clock::now();
auto durB_non = std::chrono::duration_cast<std::chrono::milliseconds>(endB_non - startB_non).count();
// -------------------- 输出结果 --------------------
std::cout << "==== 存在 key 测试 ====\n";
std::cout << "Version A (optional<Value>) : " << durA_exist << " ms, checksum=" << checksumA_exist << "\n";
std::cout << "Version B (optional<ref_wrap>) : " << durB_exist << " ms, checksum=" << checksumB_exist << "\n";
std::cout << "==== 不存在 key 测试 ====\n";
std::cout << "Version A (optional<Value>) : " << durA_non << " ms, checksum=" << checksumA_non << "\n";
std::cout << "Version B (optional<ref_wrap>) : " << durB_non << " ms, checksum=" << checksumB_non << "\n";
return 0;
}
编译运行后控制台输出如下:
ini
==== 存在 key 测试 ====
Version A (optional<Value>) : 2107 ms, checksum=5.16991e+06
Version B (optional<ref_wrap>) : 701 ms, checksum=5.16991e+06
==== 不存在 key 测试 ====
Version A (optional<Value>) : 523 ms, checksum=0
Version B (optional<ref_wrap>) : 463 ms, checksum=0
可以看到,在查找存在的 key 的场景下,使用 std::reference_wrapper
后,由于避免了对 Value
的拷贝,有非常显著的性能提升。