Mooncake Evict: 一次 std::make_pair 让 iter_ 悄悄失效
内容提要
本文讨论了 C++ 中内存池管理的一个 bug,特别是 KeyEvictInfo.iter_ 的迭代器失效问题。作者指出,虽然代码表面上看似安全,但由于使用 std::make_pair 导致的拷贝操作,实际上破坏了迭代器的有效性。这种隐蔽的语义差异使得问题难以察觉,强调了 C++ 语言的复杂性和潜在风险。
关键要点
-
KeyEvictInfo.iter_ 必须始终指向当前所在 list 的迭代器,破坏这一不变量会导致后续操作出错。
-
在 SwapOut() 函数中,使用 std::make_pair 导致了 ok_list 和 err_list 的拷贝,而不是移动。
-
std::make_pair 的使用使得两个左值被复制到返回值中,导致 list node 的身份发生变化。
-
虽然 shared_ptr 指向的对象没有被复制,但 list node 被复制后,iter_ 仍然指向旧的 node,导致迭代器失效。
-
C++ 的语义复杂性使得这种问题不易察觉,尤其是在编译通过的情况下,代码表面看似安全。
-
C++ 的隐式规则和细微差别增加了开发者的心智负担,容易导致错误,尤其是在处理迭代器失效的问题上。
延伸问答
C++ 中 KeyEvictInfo.iter_ 的迭代器失效问题是什么?
KeyEvictInfo.iter_ 必须始终指向当前 list 的迭代器,若破坏这一不变量,后续操作将出错。
为什么 std::make_pair 会导致迭代器失效?
std::make_pair 导致 ok_list 和 err_list 的拷贝,而不是移动,导致 list node 身份变化,迭代器失效。
如何避免 C++ 中的迭代器失效问题?
应确保使用移动语义而非拷贝,避免将左值传递给 std::make_pair,以保持迭代器的有效性。
C++ 的语义复杂性对开发者有什么影响?
C++ 的隐式规则和细微差别增加了开发者的心智负担,容易导致错误,特别是在处理迭代器失效时。
在 SwapOut() 函数中,如何处理 list 的元素?
在 SwapOut() 中,通过 splice 将元素从 pre_evict 移动到 ok_list 或 err_list,而不是使用 erase 和 insert。
为什么 C++ 的 move 语义不够直观?
C++ 的 move 语义常常被隐式规则和模板推导所掩盖,导致开发者误判代码的实际行为。