这可能是一个哲学问题,但我遇到了以下问题:
如果您定义了一个std :: function,并且没有正确初始化它,那么您的应用程序将崩溃,如下所示:
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 :: function实例有一个默认的构造函数?如果没有默认构造函数,则不必添加检查,就像函数的其他正常参数一样。 在那些你想要传递'可选'std :: function的'罕见'情况下,你仍然可以传递一个指向它的指针(或使用boost :: optional)。
答案 0 :(得分:14)
是的,但其他类型也是如此。例如。如果我希望我的类有一个可选的Person,那么我将我的数据成员设为Person-pointer。为什么不对std :: functions做同样的事情? std :: function有什么特别之处,它可以有一个'无效'状态?
它没有“无效”状态。它不再无效:
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
包含一个功能。它是可调用类型的持有者。因此,它可能不包含可调用类型。
答案 1 :(得分:9)
实际上,您的应用程序不应该崩溃。
§20.8.11.1班级bad_function_call [func.wrap.badcall]
1 / 当函数包装器对象没有目标时,
bad_function_call
(20.8.11.2.4)抛出类型function::operator()
的异常。
完全指定行为。
答案 2 :(得分:5)
std::function
最常见的用例之一是注册回调,在满足某些条件时调用。允许未初始化的实例可以仅在需要时注册回调,否则您将被迫始终至少传递某种无操作函数。
答案 3 :(得分:5)
答案可能是历史性的:std::function
是函数指针的替代品,函数指针具有NULL
的能力。因此,当您希望提供与函数指针的轻松兼容性时,您需要提供无效状态。
可识别的无效状态实际上并不是必需的,因为正如您所提到的,boost::optional
做得很好。所以我会说std::function
只是为了历史而存在。
答案 4 :(得分:1)
在某些情况下,您无法在构造中初始化所有内容(例如,当参数取决于对另一个构造的影响时,该影响又取决于对第一个构造的影响...)。
在这种情况下,您必须打破循环,允许稍后更正可识别的无效状态。 因此,您将第一个构造为“null”,构造第二个元素,然后重新分配第一个元素。
实际上,你可以避免检查,如果使用了一个函数 - 你在嵌入它的对象的构造函数中授予它,你将在有效的重新分配后返回。
答案 5 :(得分:0)
就像你可以将nullstate添加到没有一个的functor类型一样,你可以用一个不允许nullstate的类包装一个functor。前者需要添加状态,后者不需要新状态(仅限制)。因此,虽然我不知道std::function
设计的基本原理,但它支持最精益和最简单的设计。平均用法,无论你想要什么。
干杯&amp;第h。,
答案 6 :(得分:0)
您只需使用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);