为什么std::函数实例有默认构造函数?
Why do std::function instances have a default constructor?
这可能是一个哲学问题,但我遇到了以下问题:
如果你定义了一个std::函数,而你没有正确地初始化它,你的应用程序会崩溃,像这样:
typedef std::function<void(void)> MyFunctionType;
MyFunctionType myFunction;
myFunction();
如果函数作为参数传递,像这样:
void DoSomething (MyFunctionType myFunction)
{
myFunction();
}
当然,它也会崩溃。这意味着我必须添加这样的检查代码:
void DoSomething (MyFunctionType myFunction)
{
if (!myFunction) return;
myFunction();
}
要求这些检查让我回想起以前的C时代,在那里你还必须显式检查所有的指针参数:
void DoSomething (Car *car, Person *person)
{
if (!car) return; // In real applications, this would be an assert of course
if (!person) return; // In real applications, this would be an assert of course
...
}
幸运的是,我们可以在c++中使用引用,这可以防止我编写这些检查(假设调用者没有将nullptr的内容传递给函数):
void DoSomething (Car &car, Person &person)
{
// I can assume that car and person are valid
}
那么,为什么std::函数实例有一个默认构造函数呢?如果没有默认构造函数,您就不必像对函数的其他普通参数那样添加检查。在那些"罕见的"情况下,你想传递一个"可选的"std::函数,你仍然可以传递一个指向它的指针(或使用boost::optional)。
为真,但对于其他类型也是如此。例如,如果我想让我的类有一个可选的Person,那么我让我的数据成员成为Person-pointer。为什么不对std::函数做同样的事情呢?是什么特别的std::函数,它可以有一个"无效"的状态?
没有"无效"状态。
std::vector<int> aVector;
aVector[0] = 5;
你得到的是一个空的 function
,就像aVector
是一个空的vector
一样。对象处于一个非常明确的状态:没有数据的状态。
现在,让我们考虑一下你的"指针指向函数"的建议:
void CallbackRegistrar(..., std::function<void()> *pFunc);
你要怎么称呼它?嗯,有一件事你不能做:
void CallbackFunc();
CallbackRegistrar(..., CallbackFunc);
这是不允许的,因为CallbackFunc
是函数,而参数类型是std::function<void()>*
。这两个是不可转换的,所以编译器会报错。为了执行调用,你必须这样做:
void CallbackFunc();
CallbackRegistrar(..., new std::function<void()>(CallbackFunc));
你刚刚把new
引入了画面。你已经分配了一个资源;谁将对此负责?CallbackRegistrar
吗?显然,您可能希望使用某种智能指针,因此使用:
void CallbackRegistrar(..., std::shared_ptr<std::function<void()>> pFunc);
这是很多API的烦恼和繁琐,只是为了传递一个函数。避免这种情况的最简单方法是允许std::function
为空。就像我们允许std::vector
为空一样。就像我们允许std::string
为空一样。就像我们允许std::shared_ptr
为空一样。等等。
简单来说:std::function
包含一个函数。它是可调用类型的占位符。因此,有可能它不包含可调用类型。
实际上,您的应用程序不应该崩溃。
§20.8.11.1 Class bad_function_call [funcs .wrap.badcall]
1/当函数包装器对象没有目标时,
function::operator()
(20.8.11.2.4)抛出bad_function_call
类型的异常。
行为是完全指定的。
std::function
最常见的用例之一是注册回调,当满足某些条件时调用。允许未初始化的实例使得只在需要时注册回调成为可能,否则您将被迫总是传递至少某种无操作函数。
答案可能是历史上的:std::function
意味着函数指针的替代品,而函数指针有能力成为NULL
。因此,当您想要提供与函数指针的简单兼容性时,您需要提供一个无效状态。
可识别的无效状态并不是真正必要的,因为正如您提到的,boost::optional
可以很好地完成这项工作。所以我想说,std::function
只是为了历史而存在。
在某些情况下,您不能在构造时初始化所有内容(例如,当参数依赖于另一个构造的效果时,该构造又依赖于第一个构造的效果…)。
在这种情况下,您必须中断循环,承认可识别的无效状态,以便稍后纠正。因此,您将第一个元素构造为"null",构造第二个元素,并重新分配第一个元素。实际上,您可以避免检查,如果在使用函数的地方,您在嵌入它的对象的构造函数中授予它,则您将始终在有效的重新赋值后返回。
就像你可以给一个没有nullstate的函子类型添加一个nullstate一样,你可以用一个不承认nullstate的类来包装一个函子。前者需要添加状态,后者不需要添加新的状态(只有一个限制)。因此,虽然我不知道std::function
设计的基本原理,但它支持最精简的&意思用法,不管你想要什么。
干杯,hth。
您只需使用std::function进行回调,您可以使用一个简单的模板辅助函数,如果它不为空,则将其参数转发给处理程序:
template <typename Callback, typename... Ts>
void SendNotification(const Callback & callback, Ts&&... vs)
{
if (callback)
{
callback(std::forward<Ts>(vs)...);
}
}
并按以下方式使用:
std::function<void(int, double>> myHandler;
...
SendNotification(myHandler, 42, 3.15);
- 为什么在没有显式默认构造函数的情况下,将另一个结构封装在联合中作为成员的结构不能编译
- 初始化具有非默认构造函数的std::数组项的更好方法
- 具有默认模板类型的默认构造函数的类型推导
- 如何使用非默认构造函数实例化模板化类
- 有没有一种代码密度较低的方法来使用非默认构造函数初始化数组?
- 声明没有默认构造函数的字段
- 没有默认构造函数作为模板参数的自定义比较器
- C++17 没有默认构造函数的地图放置(私有默认构造函数)
- 使用移动调用对等构造函数unique_ptr默认构造函数
- C++复制构造函数和默认构造函数
- 将向量从 N1 缩小到 N2 项,而不触发默认构造函数并仅使用 move 语义
- 为什么即使我调用参数化构造函数也会调用默认构造函数?
- 具有非默认构造函数的单例类
- 在 C++ 中声明 const 对象需要用户定义的默认构造函数.如果我有一个可变成员变量,为什么不呢?
- 如何处理没有默认构造函数但在另一个构造函数中构造的对象?
- 在C++中使用默认构造函数初始化对象的不同方法
- 在没有默认构造函数的情况下创建的派生对象
- 强制使用默认构造函数对成员进行未初始化的声明
- 使用默认构造函数初始化对象的不同方法
- 创建类类型的动态分配数组,其中类不得具有默认构造函数