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

发布于 2024-12-06 06:36:22 字数 1155 浏览 3 评论 0原文

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

如果您定义了一个 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 实例有默认构造函数?如果没有默认构造函数,您就不必添加检查,只需就像 a 的其他正常参数一样 功能。 在那些“罕见”的情况下,您想要传递“可选”std::function,您仍然可以传递指向它的指针(或使用 boost::optional)。

This is probably a philosophical question, but I ran into the following problem:

If you define an std::function, and you don't initialize it correctly, your application will crash, like this:

typedef std::function<void(void)> MyFunctionType;
MyFunctionType myFunction;
myFunction();

If the function is passed as an argument, like this:

void DoSomething (MyFunctionType myFunction)
   {
   myFunction();
   }

Then, of course, it also crashes. This means that I am forced to add checking code like this:

void DoSomething (MyFunctionType myFunction)
   {
   if (!myFunction) return;
   myFunction();
   }

Requiring these checks gives me a flash-back to the old C days, where you also had to check all pointer arguments explicitly:

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
   ...
   }

Luckily, we can use references in C++, which prevents me from writing these checks (assuming that the caller didn't pass the contents of a nullptr to the function:

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

So, why do std::function instances have a default constructor? Without default constructor you wouldn't have to add checks, just like for other, normal arguments of a function.
And in those 'rare' cases where you want to pass an 'optional' std::function, you can still pass a pointer to it (or use boost::optional).

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

雪落纷纷 2024-12-13 06:36:22

确实如此,但对于其他类型也是如此。例如,如果我希望我的类有一个可选的 Person,那么我将我的数据成员设为 Person 指针。为什么不对 std::functions 做同样的事情呢? std::function 有什么特别之处以至于它可以具有“无效”状态?

它没有“无效”状态。它并不比这更无效:

std::vector<int> aVector;
aVector[0] = 5;

你拥有的是一个函数,就像aVector是一个空向量 >。该对象处于一种非常明确的状态:没有数据的状态。

现在,让我们考虑一下您的“函数指针”建议:

void CallbackRegistrar(..., std::function<void()> *pFunc);

您必须如何调用它?好吧,有一件事你不能做:

void CallbackFunc();
CallbackRegistrar(..., CallbackFunc);

这是不允许的,因为 CallbackFunc 是一个函数,而参数类型是 std::function;*。这两个是不可转换的,所以编译器会抱怨。因此,为了进行调用,您必须执行以下操作:

void CallbackFunc();
CallbackRegistrar(..., new std::function<void()>(CallbackFunc));

您刚刚将 new 引入到图片中。您已分配资源;谁将为此负责? 回调注册器?显然,您可能想要使用某种智能指针,因此您会更加混乱界面:

void CallbackRegistrar(..., std::shared_ptr<std::function<void()>> pFunc);

这会带来很多 API 烦恼和麻烦,只是为了传递函数。避免这种情况的最简单方法是允许 std::function。就像我们允许 std::vector 为空一样。就像我们允许 std::string 为空一样。就像我们允许 std::shared_ptr 为空一样。等等。

简而言之:std::function 包含一个函数。它是可调用类型的持有者。因此,它有可能不包含可调用类型。

True, but this is also true for other types. E.g. if I want my class to have an optional Person, then I make my data member a Person-pointer. Why not do the same for std::functions? What is so special about std::function that it can have an 'invalid' state?

It does not have an "invalid" state. It is no more invalid than this:

std::vector<int> aVector;
aVector[0] = 5;

What you have is an empty function, just like aVector is an empty vector. The object is in a very well-defined state: the state of not having data.

Now, let's consider your "pointer to function" suggestion:

void CallbackRegistrar(..., std::function<void()> *pFunc);

How do you have to call that? Well, here's one thing you cannot do:

void CallbackFunc();
CallbackRegistrar(..., CallbackFunc);

That's not allowed because CallbackFunc is a function, while the parameter type is a std::function<void()>*. Those two are not convertible, so the compiler will complain. So in order to do the call, you have to do this:

void CallbackFunc();
CallbackRegistrar(..., new std::function<void()>(CallbackFunc));

You have just introduced new into the picture. You have allocated a resource; who is going to be responsible for it? CallbackRegistrar? Obviously, you might want to use some kind of smart pointer, so you clutter the interface even more with:

void CallbackRegistrar(..., std::shared_ptr<std::function<void()>> pFunc);

That's a lot of API annoyance and cruft, just to pass a function around. The simplest way to avoid this is to allow std::function to be empty. Just like we allow std::vector to be empty. Just like we allow std::string to be empty. Just like we allow std::shared_ptr to be empty. And so on.

To put it simply: std::function contains a function. It is a holder for a callable type. Therefore, there is the possibility that it contains no callable type.

子栖 2024-12-13 06:36:22

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

§ 20.8.11.1 类 bad_function_call [func.wrap.badcall]

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

该行为被完美指定。

Actually, your application should not crash.

§ 20.8.11.1 Class bad_function_call [func.wrap.badcall]

1/ An exception of type bad_function_call is thrown by function::operator() (20.8.11.2.4) when the function wrapper object has no target.

The behavior is perfectly specified.

世界如花海般美丽 2024-12-13 06:36:22

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

One of the most common use cases for std::function is to register callbacks, to be called when certain conditions are met. Allowing for uninitialized instances makes it possible to register callbacks only when needed, otherwise you would be forced to always pass at least some sort of no-op function.

蘸点软妹酱 2024-12-13 06:36:22

答案可能是历史性的:std::function 是作为函数指针的替代品,并且函数指针具有为 NULL 的能力。因此,当您想要提供与函数指针的简单兼容性时,您需要提供无效状态。

可识别的无效状态并不是真正必要的,因为正如您所提到的,boost::Optional 可以很好地完成这项工作。所以我想说 std::function 的存在只是为了历史。

The answer is probably historical: std::function is meant as a replacement for function pointers, and function pointers had the capability to be NULL. So, when you want to offer easy compatibility to function pointers, you need to offer an invalid state.

The identifiable invalid state is not really necessary since, as you mentioned, boost::optional does that job just fine. So I'd say that std::function's are just there for the sake of history.

巾帼英雄 2024-12-13 06:36:22

在某些情况下,您无法在构造时初始化所有内容(例如,当一个参数取决于对另一个构造的影响,而另一个构造又取决于对第一个构造的影响时......)。

在这种情况下,您必须打破循环,承认稍后纠正可识别的无效状态。
因此,您将第一个元素构造为“null”,构造第二个元素,然后重新分配第一个元素。

实际上,您可以避免检查,如果在使用函数的情况下您在嵌入该函数的对象的构造函数内授予该函数,那么您将始终在有效的重新分配后返回。

There are cases where you cannot initialize everything at construction (for example, when a parameter depends on the effect on another construction that in turn depends on the effect on the first ...).

In this cases, you have necessarily to break the loop, admitting an identifiable invalid state to be corrected later.
So you construct the first as "null", construct the second element, and reassign the first.

You can, actually, avoid checks, if -where a function is used- you grant that inside the constructor of the object that embeds it, you will always return after a valid reassignment.

追我者格杀勿论 2024-12-13 06:36:22

就像可以将空状态添加到没有空状态的仿函数类型中一样,您可以使用不允许空状态的类来包装仿函数。前者需要添加状态,后者不需要新状态(仅是一个限制)。因此,虽然我不知道 std::function 设计的基本原理,但它支持最精简的设计。意思是用法,无论你想要什么。

干杯&呵呵,,

In the same way that you can add a nullstate to a functor type that doesn't have one, you can wrap a functor with a class that does not admit a nullstate. The former requires adding state, the latter does not require new state (only a restriction). Thus, while i don't know the rationale of the std::function design, it supports the most lean & mean usage, no matter what you want.

Cheers & hth.,

梦情居士 2024-12-13 06:36:22

您只需使用 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);

You just use std::function for callbacks, you can use a simple template helper function that forwards its arguments to the handler if it is not empty:

template <typename Callback, typename... Ts>
void SendNotification(const Callback & callback, Ts&&... vs)
{
    if (callback)
    {
        callback(std::forward<Ts>(vs)...);
    }
}

And use it in the following way:

std::function<void(int, double>> myHandler;
...
SendNotification(myHandler, 42, 3.15);
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文