为何std::function_ref允许传入将亡值仿函数而非直接禁止?
在cppreference的std::function_ref文档中,存在如下重载构造函数:
template<class F> function_ref(F&& f) noexcept;
该构造函数通过std::addressof(f)初始化绑定实体,但允许调用者传入将亡值仿函数,进而导致使用悬垂引用调用已销毁的仿函数,示例代码如下:
#include <functional> std::function_ref<int(int)> f() { auto n = 1; auto const fn = [n](int m) { return n + m; }; return std::function_ref<int(int)>(std::move(fn)); // 匹配该构造函数 } int main() { auto fn_ref = f(); // fn_ref现在引用已销毁的仿函数! return fn_ref(2); // 崩溃! }
而std::ref与std::cref针对此类问题做了更安全的设计,通过删除右值引用重载来避免风险:
template<class T> void ref(const T&&) = delete; template<class T> void cref(const T&&) = delete;
疑问:为何C++标准不直接拒绝右值引用(包括&&和const&&)类型的仿函数参数?
回答
支持临时函数对象的合法使用场景:有些临时函数对象的生命周期足够覆盖
function_ref的使用周期,比如在同一表达式内直接调用:// 合法场景:临时lambda的生命周期到表达式结束,调用时对象仍存在 int result = std::function_ref<int(int)>([](int x){return x*2;})(5);如果直接禁止右值参数,这类合法的便捷用法也会被阻断。
灵活性与安全性的权衡:
function_ref的设计初衷是作为轻量级的函数引用包装,追求极致灵活和零开销。标准委员会选择保留右值重载,是为了不限制用户在明确知晓生命周期安全的情况下使用临时对象——毕竟function_ref本身就是为能确保被引用对象生命周期长于包装器的场景设计的,逻辑类似原始引用,把生命周期管理的责任交给用户。与std::ref/cref的设计目标差异:
std::ref和std::cref的核心目的是在需要传递引用的场景(比如std::bind或标准算法)中避免对象拷贝,它们的使用场景偏向“绑定已有对象的引用”,而非接受临时对象,所以删除右值重载符合其定位。但function_ref的定位更宽泛,既可以绑定左值,也允许在安全前提下绑定临时对象,因此不能照搬ref/cref的限制。用户可自行规避风险:如果需要强制避免右值绑定,用户可以通过封装、静态断言或C++20的概念约束来限制
function_ref的构造参数为左值。标准没有强制禁止,是为了给用户留下更多灵活选择的空间。
内容的提问来源于stack exchange,提问作者xmllmx

