为什么std :: function实例有一个默认的构造函数?

时间:2011-09-23 09:24:42

标签: c++ std-function

这可能是一个哲学问题,但我遇到了以下问题:

如果您定义了一个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)。

7 个答案:

答案 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);