如何在C++11中正确检查std::function是否为空?

134

我在想如何正确地检查一个std::function是否为空。考虑以下例子:

class Test {
    std::function<void(int a)> eventFunc;

    void registerEvent(std::function<void(int a)> e) {
        eventFunc = e;
    }

    void doSomething() {
        ...
        eventFunc(42);
    }
};

这段代码在MSVC中编译没有问题,但是如果我调用doSomething()时没有初始化eventFunc,代码显然会崩溃。这是可以预料的,但我想知道eventFunc的值是多少?调试器显示'empty'。所以我使用了简单的if语句来解决这个问题:

   void doSomething() {
        ...
        if (eventFunc) {
            eventFunc(42);
        }
   }

这样写是可以运行的,但我仍然想知道未初始化的std::function的值是什么?我想写if (eventFunc != nullptr),但显然std::function并不是一个指针。

为什么纯粹的if语句可以工作?其中的神奇之处是什么?这是否是检查它的正确方式?


9
请注意,eventFunc 不是一个 lambda 表达式,它是一个 std::function 对象。你可以将 lambda 表达式存储在 std::function 对象中,但它们并不完全相同。 - templatetypedef
3
你说得对,我改了标题以避免混淆。谢谢。 - NightElfik
3个回答

139
您需要检查 std::function 中是否存储了一个可调用目标,而不是检查空 lambda。这个检查是明确定义的,并且有效,因为 std::function::operator bool 允许在需要布尔值(如 if 语句中的条件表达式)的上下文中进行隐式转换。
此外,"空 lambda" 的概念并不太合理。在幕后,编译器将 lambda 表达式转换为一个带有捕获变量的数据成员的结构体(或类)定义。还定义了一个公共函数调用运算符,这就是允许您调用 lambda 的东西。那么一个空的 lambda 是什么?
另外,如果您愿意,也可以编写 if(eventFunc != nullptr),它与您在问题中的代码等效。 std::function 定义 用于与 nullptr_t 比较的 operator==operator!= 重载。

1
== nullptr 不是做同样的事情吗?看起来应该有一个重载 == 运算符,使得“空”的 std::functionnullptr 比较为 true:http://www.cplusplus.com/reference/functional/function/operators/ - Kyle Strand
3
是的,与 nullptr 进行比较也可以起到作用,上述问题中的 if(eventFunc != nullptr) 相当于 if(eventFunc) - Praetorian
5
从技术上讲,std::function::operator bool 不允许隐式转换为 bool。毕竟它被标记为 explicit,但标准对某些期望布尔表达式的语言构造进行了例外,称之为“上下文转换为 bool”。您可以在此处找到相关片段的标准解释和解释:http://chris-sharpe.blogspot.com/2013/07/contextually-converted-to-bool.html - bcrist
2
@bcrist 是的,我知道布尔转换运算符是explicit,这就是为什么我小心地说明*允许在需要布尔值的上下文中进行隐式转换到bool*。这正是问题代码中正在发生的事情。 - Praetorian
7
我想表达的意思是,“隐式转换”这个词组在标准中有一个非常具体的含义,它与“上下文转换为bool值”的含义完全不同。这里不存在“Is-a”的关系。我理解初学者可能不需要立即了解隐式/显式/上下文转换之间的区别,但更好的方式是下意识地学习正确的术语,而不是以后要打破旧习惯。 - bcrist

37

点击此处查看std::function::operator bool

返回值

  • 当对象可调用时,返回true。
  • 否则返回false(对象为空函数)。

示例

// function::operator bool example
#include <iostream>     // std::cout
#include <functional>   // std::function, std::plus

int main ()
{
    std::function<int(int,int)> foo; // empty
    
    if(foo)
    {
        std::cout << "[1] foo is NOT empty" << std::endl;
    }
    else
    {
        std::cout << "[1] foo is EMPTY" << std::endl;
    }
    
    // Now we can assign example
    foo = std::plus<int>();
  
    if(foo)
    {
        std::cout << "[2] foo is NOT empty" << std::endl;
    }
    else
    {
        std::cout << "[2] foo is EMPTY" << std::endl;
    }
  
    // Example use with ternary operator (https://www.cprogramming.com/reference/operators/ternary-operator.html)
    std::cout << "[3] foo is " << (foo ? "callable" : "NOT callable") << std::endl;
    
    // make it empty
    foo = {};
    std::cout << "[4] foo is " << (foo ? "callable" : "NOT callable") << std::endl;
    
    foo = [](int a, int b){ return a+b; };
    std::cout << "[5] foo is " << (foo ? "callable" : "NOT callable") << std::endl;
    
    return foo(1, 1) == 2; // return 1
}

输出

[1] foo is EMPTY
[2] foo is NOT empty
[3] foo is callable
[4] foo is NOT callable
[5] foo is callable

35
我认为如果去掉 swap(),这个答案会更加清晰。在意识到之前,我一直以为输出是相反的。 - cp.engr
cplusplus.com不是比cppreference.com好得多了吗?我真的认为前者的示例更清晰明了。为什么那么多人转向后者呢?我很好奇! - Vassilis
@Vassilis 很长一段时间以来,C++没有新标准的文档。cppreference是最新的,具有正式的定义。我建议在学习阶段使用cplusplus。 - Burak

7

让我提供一个清晰的答案。

您可以使用 std::function::operator bool 检查 std::function 是否为空。

true:如果对象是可调用的。
false:否则(对象是一个空函数)

示例

#include <iostream>
#include <functional>

int main ()
{
    std::function<int(int,int)> foo = std::plus<int>();//assigned: not empty
    std::function<int(int,int)> bar;//not assigned: empty

    std::cout << "foo is " << (foo ? "not empty" : "empty") << ".\n";
    std::cout << "bar is " << (bar ? "not empty" : "empty") << ".\n";

    return 0;
}

输出

foo不为空。
bar为空。


2
你的结果字符串被交换了。 - Sophit
@Sophit 你确定吗?;) - zwcloud
1
你的注释说foo不为空,但实际输出与之不符。我同意你的注释。 - Sophit

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接