完美转发场景下,代码中a和b的拷贝是否不可避免?求替代方案
解答你的TEST_EQ模板问题
首先明确:你完全不需要对a和b进行拷贝(或移动)——当前代码里的拷贝/移动是因为auto的推导规则导致的,属于不必要的开销,甚至可能在不可拷贝/移动的类型上编译失败。
为什么当前代码会触发拷贝/移动?
看你写的这两行:
auto a = forward<S>(expected); auto b = forward<T>(received);
当传入的是右值(比如临时对象)时,std::forward<S>(expected)会返回一个右值。此时auto会推导为值类型(而非引用),所以编译器会调用该类型的拷贝构造函数(如果支持移动构造,会优先调用移动构造)来创建a和b的副本。这完全是多余的,因为我们只是要做一次相等性比较,不需要持有参数的副本。
替代实现方案
根据你的需求(是否需要在比较失败后打印参数),有几种不同的优化方案:
方案1:直接使用转发后的参数比较(无任何拷贝/移动)
如果你的==运算符不会修改参数(这是绝大多数情况),可以直接用转发后的参数完成比较和打印,完全不需要中间变量:
template<typename S, typename T> static bool TEST_EQ(S&& expected, T&& received, const std::string& error_msg) { bool test = std::forward<S>(expected) == std::forward<T>(received); if (!test) { // 注意:如果你的==运算符会移动参数,这里打印会出问题,但标准的==不会这么做 std::cout << "Failed: expected:" << std::forward<S>(expected) << " received:" << std::forward<T>(received) << " - " << error_msg << '\n'; } return test; }
这个方案最简洁,完全避免了拷贝/移动,性能最优。
方案2:用万能引用绑定参数(安全复用参数)
如果你担心多次调用std::forward可能带来的问题(比如某些自定义==会修改参数),可以用auto&&(万能引用)绑定转发后的结果,这样既不会拷贝,又能安全复用参数:
template<typename S, typename T> static bool TEST_EQ(S&& expected, T&& received, const std::string& error_msg) { // auto&&会自动绑定左值引用或右值引用,无拷贝 auto&& a = std::forward<S>(expected); auto&& b = std::forward<T>(received); bool test = a == b; if (!test) { std::cout << "Failed: expected:" << a << " received:" << b << " - " << error_msg << '\n'; } return test; }
这里auto&&的推导规则会完美匹配std::forward的结果:如果传入的是左值,a/b就是左值引用;如果是右值,就是右值引用,完全没有不必要的拷贝。
方案3:对右值参数做拷贝(极端安全场景)
如果你的场景中,==运算符可能会移动右值参数,或者你需要在比较后确保参数的状态不受影响,可以针对右值参数做选择性拷贝,左值参数仍然用引用:
#include <type_traits> template<typename S, typename T> static bool TEST_EQ(S&& expected, T&& received, const std::string& error_msg) { // 左值用引用,右值拷贝成临时对象 using ExpectedDecay = std::decay_t<S>; auto a = std::conditional_t<std::is_lvalue_reference_v<S>, S&&, ExpectedDecay>( std::forward<S>(expected) ); using ReceivedDecay = std::decay_t<T>; auto b = std::conditional_t<std::is_lvalue_reference_v<T>, T&&, ReceivedDecay>( std::forward<T>(received) ); bool test = a == b; if (!test) { std::cout << "Failed: expected:" << a << " received:" << b << " - " << error_msg << '\n'; } return test; }
这个方案会在必要时(右值参数)创建副本,确保后续使用(比如打印)的安全性,同时避免了左值参数的拷贝。
总结
- 完全不需要强制拷贝
a和b,当前代码的拷贝是auto推导导致的冗余操作; - 优先选择方案1或方案2,在绝大多数场景下足够高效且安全;
- 只有在极端安全需求下才需要方案3的选择性拷贝。
内容的提问来源于stack exchange,提问作者Bryan Fok
相关产品推荐
相关产品推荐

