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

Pat*_*ick 13 c++ std-function

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

如果您定义了一个std :: function,并且没有正确初始化它,那么您的应用程序将崩溃,如下所示:

typedef std::function<void(void)> MyFunctionType;
MyFunctionType myFunction;
myFunction();
Run Code Online (Sandbox Code Playgroud)

如果函数作为参数传递,如下所示:

void DoSomething (MyFunctionType myFunction)
   {
   myFunction();
   }
Run Code Online (Sandbox Code Playgroud)

然后,当然,它也崩溃了.这意味着我被迫添加这样的检查代码:

void DoSomething (MyFunctionType myFunction)
   {
   if (!myFunction) return;
   myFunction();
   }
Run Code Online (Sandbox Code Playgroud)

需要这些检查让我回到旧的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
   ...
   }
Run Code Online (Sandbox Code Playgroud)

幸运的是,我们可以在C++中使用引用,这阻止我编写这些检查(假设调用者没有将nullptr的内容传递给函数:

void DoSomething (Car &car, Person &person)
   {
   // I can assume that car and person are valid
   }
Run Code Online (Sandbox Code Playgroud)

那么,为什么std :: function实例有一个默认的构造函数?如果没有默认构造函数,则不必添加检查,就像函数的其他正常参数一样.在那些你想要传递'可选'std :: function的'罕见'情况下,你仍然可以传递一个指向它的指针(或者使用boost :: optional).

Nic*_*las 15

没错,但其他类型也是如此.例如,如果我希望我的类有一个可选的Person,那么我将我的数据成员设为Person-pointer.为什么不对std :: functions做同样的事情?std :: function有什么特别之处,它可以有一个'无效'状态?

它没有"无效"状态.它不再无效:

std::vector<int> aVector;
aVector[0] = 5;
Run Code Online (Sandbox Code Playgroud)

你拥有的是空的 function,就像aVector是空的vector.对象处于非常明确的状态:没有数据的状态.

现在,让我们考虑一下你的"函数指针"建议:

void CallbackRegistrar(..., std::function<void()> *pFunc);
Run Code Online (Sandbox Code Playgroud)

你怎么称呼它?嗯,这是你不能做的一件事:

void CallbackFunc();
CallbackRegistrar(..., CallbackFunc);
Run Code Online (Sandbox Code Playgroud)

这是不允许的,因为它CallbackFunc是一个函数,而参数类型是a std::function<void()>*.这两个不可转换,所以编译器会抱怨.所以为了打电话,你必须这样做:

void CallbackFunc();
CallbackRegistrar(..., new std::function<void()>(CallbackFunc));
Run Code Online (Sandbox Code Playgroud)

你刚刚介绍new了图片.您已分配资源; 谁将负责呢?CallbackRegistrar?显然,您可能想要使用某种智能指针,因此您可以使用以下内容使界面更加混乱:

void CallbackRegistrar(..., std::shared_ptr<std::function<void()>> pFunc);
Run Code Online (Sandbox Code Playgroud)

这是一个很多API的烦恼和残酷,只是传递一个功能.避免这种情况的最简单方法是允许std::function.就像我们允许std::vector空洞一样.就像我们允许std::string空洞一样.就像我们允许std::shared_ptr空洞一样.等等.

简单地说:std::function 包含一个函数.它是可调用类型的持有者.因此,它可能不包含可调用类型.

  • 显然,你需要`boost :: optional <std :: function <`.问题是您可以从"非可选"类型构造"可选类型",但反之亦然.因此,默认情况下,类型应为"非可选". (3认同)
  • @MSalters:是的,那会有用.但遗憾的是`optional`不是C++标准库的一部分.对于那些不允许使用Boost的可怜的,不幸的灵魂来说,根本不是一个选择. (3认同)
  • 我认为你的矢量示例是不同的.除了赋值之外,对空std ::函数没什么用处.它不"包含"一个值.构造时,矢量已经是矢量.您可以使用它的大部分接口(例如push_back()).在函数的情况下,我会说默认构造函数实际上没有意义. (2认同)

Mat*_* M. 9

实际上,您的应用程序不应该崩溃.

§20.8.11.1类bad_function_call [func.wrap.badcall]

1 /当函数包装器对象没有目标时bad_function_call,function::operator()(20.8.11.2.4)抛出类型异常.

行为是完美的.

  • 它可以被指定,但规范还说程序在离开`main`的情况下终止(即:崩溃).如果你没有抓住它会发生什么. (2认同)
  • @NicolBolas:你是对的,但我并不认为这是一次崩溃,因为许多操作可能引发异常:为`std :: function`分配内容的简单事实可能会执行内存分配.因此,不处理异常与"std :: function"可能无法使用回调初始化这一事实完全无关. (2认同)

Nic*_*tti 5

最常见的用例之一std::function是注册回调,在满足某些条件时调用.允许未初始化的实例可以仅在需要时注册回调,否则您将被迫始终至少传递某种无操作函数.

  • 在我看来,你应该看到std :: function是一种用于callables的智能指针.您不希望使用指向shared_ptr的指针,不是吗? (4认同)

thi*_*ton 5

答案可能是历史的:std::function用作函数指针的替代品,函数指针有能力NULL.因此,当您希望提供与函数指针的轻松兼容性时,您需要提供无效状态.

可识别的无效状态并非真正必要,因为正如您所提到的boost::optional那样,这项工作就可以了.所以我会说那std::function是为了历史而存在的.