完美转发是 C++ 模板编程中的一项重要技术,它允许函数模板将其参数原封不动地转发给其他函数,保持参数的值类别(左值/右值)和类型不变。这是实现通用包装函数、工厂模式等高级功能的基础。
42
, std::move(x)
)C++ 的引用折叠规则决定了多重引用的最终类型:
T& &
→ T&
T& &&
→ T&
T&& &
→ T&
T&& &&
→ T&&
T&&
在模板推导时可能是左值引用或右值引用:
template <typename T>
void foo(T&& arg); // arg可以是左值或右值引用
完美转发需要两个关键组件:
T&&
接受任意类型的引用template <typename T>
void wrapper(T&& arg)
{
// 保持arg的原始值类别转发
target(std::forward<T>(arg));
}
std::forward
是一个条件转换:
template <typename T>
T&& forward(typename std::remove_reference<T>::type& arg)
{
return static_cast<T&&>(arg);
}
T
是左值引用时,返回左值引用T
是非引用或右值引用时,返回右值引用template <typename T, typename... Args>
std::unique_ptr<T> make_unique(Args&&... args)
{
return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
}
template <typename F, typename... Args>
auto wrapper(F&& f, Args&&... args)
{
return std::forward<F>(f)(std::forward<Args>(args)...);
}
class MyClass
{
public:
template <typename... Args>
MyClass(Args&&... args) : data(std::forward<Args>(args)...) {}
private:
SomeType data;
};
问题:当参数是花括号初始化列表时
wrapper({1, 2, 3}); // 编译错误
解决:明确指定类型或使用 auto
wrapper(std::initializer_list<int>{1, 2, 3});
template <typename... Args>
void forward_all(Args&&... args)
{
other_function(std::forward<Args>(args)...);
}
不必要的转发会增加编译时间,只在确实需要保持值类别时才使用完美转发。
明确标记转发参数:使用 Args&&
和 std::forward
保持参数const正确性:
template <typename T>
void foo(const T&&); // 不是通用引用!
配合移动语义使用:
template <typename T>
void sink(T&& arg)
{
stored_value = std::forward<T>(arg);
}
注意生命周期:转发后不要使用已移动的对象
template <typename... Args>
void log_all(Args&&... args)
{
(std::cout << ... << std::forward<Args>(args)) << '\n';
}
template <std::invocable F, typename... Args>
auto call(F&& f, Args&&... args)
{
return std::forward<F>(f)(std::forward<Args>(args)...);
}
完美转发是 C++ 模板元编程的核心技术之一,正确使用可以写出既通用又高效的代码,但需要深入理解其原理才能避免常见陷阱。