为什么std::函数实例有默认构造函数?

Why do std::function instances have a default constructor?

本文关键字:默认 构造函数 实例 std 函数 为什么      更新时间:2023-10-16

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

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