接受可移动右值引用的函数在操作顺序不确定时,如何安全实现多次使用?
Great question! The core issue here is that C++ doesn't specify the evaluation order of function arguments, so using std::move(str) in one argument and str in another is a recipe for undefined behavior—you never know if the moved-from str will be used before or after the move operation.
Let's break down your options beyond the explicit copy approach you already found:
Option 1: Split the operation into two steps (no extra copy if insertion fails)
Instead of using try_emplace in one go, split it into inserting the key first, then moving the value into the entry only if the insertion happened. This avoids an unnecessary copy if the key already exists in the map:
void foo(std::string&& str) { std::unordered_map<std::string, std::string> map; auto [it, was_inserted] = map.try_emplace(str); // Copy the key if (was_inserted) { it->second = std::move(str); // Move the original string into the value } }
This gives you one move operation, and it's safe because we only use str after copying it for the key—even if we move it afterward, the key copy is already safely stored in the map.
Option 2: Use a tuple to enforce evaluation order (double move, similar to your copy approach)
If you want to stick with a single try_emplace call, you can use a tuple to explicitly control the order of copying and moving before passing to the function. Tuples evaluate their elements in order, so we can first copy str into the tuple's first element, then move str into the second:
#include <tuple> #include <functional> // For std::apply void foo(std::string&& str) { std::unordered_map<std::string, std::string> map; // First copy str to the tuple's first element, then move str to the second auto args = std::make_tuple(str, std::move(str)); // Use std::apply to pass the tuple elements as arguments to try_emplace std::apply([&map](auto&& key, auto&& val) { map.try_emplace(std::forward<decltype(key)>(key), std::forward<decltype(val)>(val)); }, std::move(args)); }
This ends up doing two moves (from the tuple to the map), just like your original copy-then-move approach, but wraps it in a tuple to make the evaluation order explicit.
Your original approach is totally valid too!
The explicit copy you wrote is straightforward and easy to read—don't discount it. It's perfectly safe and gets the job done with minimal cognitive overhead:
void foo(std::string&& str) { std::unordered_map<std::string, std::string> map; std::string copy = str; map.try_emplace(std::move(copy), std::move(str)); // Two safe moves }
Why the "obvious" one-liners are undefined behavior
Just to recap why those three invalid attempts are dangerous: if the compiler evaluates the std::move(str) argument first, str becomes a moved-from object (valid but unspecified state), and using it in the other argument could lead to anything from garbage values to crashes. The standard gives no guarantees about which argument gets evaluated first, so we can't rely on any order.
内容的提问来源于stack exchange,提问作者Zereges

