【问题标题】:Strange behavior with std::functionstd::function 的奇怪行为
【发布时间】:2013-10-24 22:58:56
【问题描述】:

我正在使用 C++11 库中的标准函数包装器,我发现它的布尔运算符出现了一些奇怪的行为。如果我创建一个 std::function 对象,布尔运算符将返回 false。如果我将nullptr 分配给对象并再次检查,这仍然是正确的。当我为其分配一个已转换为函数指针的 void 指针时,就会出现问题。考虑以下程序:

#include <functional>
#include <iostream>

void* Test() {
    return nullptr;
}

int main(int argc, char* argv[]) {
    std::function<void()> foo;
    std::cout << !!foo << std::endl;

    foo = nullptr;
    std::cout << !!foo << std::endl;

    foo = reinterpret_cast<void(*)()>(Test());
    std::cout << !!foo << std::endl;

    return 0;
}

我期望的输出是0 0 0,但结果是0 0 1(参见demo)。任何人都可以解释为什么布尔运算符在包含一个空的、不可调用的函数指针时返回 true 吗?还请提及在std::function 中检查nullptr 的解决方法

注意:我已经尝试过检查目标是否为空(使用foo.target&lt;void*&gt;() == nullptr)而不是使用布尔运算符,但似乎无论函数对象包含什么,目标始终为空(即使调用函数对象非常好)。

【问题讨论】:

标签: c++ c++11 std-function


【解决方案1】:

对我来说似乎是一个错误。首先,这是一个simplified example,它不玩任何有演员表的游戏:

#include <functional>
#include <iostream>

typedef void (*VF)();

VF Test() {
    return nullptr;
}

int main(int argc, char* argv[]) {
    std::function<void()> foo(Test());
    std::cout << !!foo << std::endl;
    return 0;
}

它仍然使用 GCC 打印 1。它不应该:

20.8.11.2.1
template&lt;class F&gt; function(F f);
template &lt;class F, class A&gt; function(allocator_arg_t, const A&amp; a, F f);
7 需要: F 应为CopyConstructible。 f 应为 Callable (20.8.11.2) 参数类型ArgTypes 和返回类型R。复制构造函数和 A 的析构函数不会抛出异常。
8 后置条件: !*this 如果以下任何一项成立:

  • f 是一个 NULL 函数指针。
  • f 是一个指向成员的 NULL 指针。
  • F 是函数类模板的一个实例,!f

【讨论】:

【解决方案2】:

我不认为代码在做你认为它做的事情。这一行:

foo = reinterpret_cast<void(*)()>(Test());

表示您收到来自Test() 的void*。然后,您继续 reinterpret_cast 这个 pointer-to-object 进入 pointer-to-function。这是不允许的,因此代码会产生未定义的行为,因此编译器的任何输出都是有效的。

标准的相关部分是

5.2.10 重新解释演员表 [expr.reinterpret.cast]

8 有条件地支持将函数指针转换为对象指针类型或反之亦然。这种转换的含义是实现定义的,除非实现支持双向转换,将一种类型的纯右值转换为另一种类型并返回,可能具有不同的 cv 限定,应产生原始指针值。

9 将空指针值(4.10)转换为目标类型的空指针值。 [注意: std::nullptr_t 类型的空指针常量不能转换为指针类型,整数类型的空指针常量不一定要转换为空指针值。 ——结束注释 ]

和

4.10 指针转换 [conv.ptr]

1空指针常量 是整数类型的整数常量表达式 (5.19) 纯右值,其计算结果为零或std::nullptr_t 类型的纯右值。空指针常量可以转换为指针类型;结果是 空指针值 该类型,并且可与对象指针或函数指针类型的所有其他值区分开来。

(强调我的)

这是一个简化的测试用例,将std::function(及其可能的错误)排除在外:

#include <iostream>

int main() {
    using fp_t = void(*)();
    void* vn = nullptr;
    fp_t foo = reinterpret_cast<fp_t>(vn); // GCC: warning, Clang: silence
    //fp_t foo = reinterpret_cast<fp_t>(nullptr); // error (GCC and Clang!)
    std::cout << !!foo << std::endl;
}

Live example

【讨论】:

  • 我不确定是 UB,参见 [expr.reinterpret.cast]/8+9 (8 表示它是实现定义的,但作为一般规则,nullptr 值 -> nullptr 值)
  • @DyP /9 不适用于 AFAICT,Test() 返回 void*,而不是 0 或 std::nullptr_t。查看 /8,我认为它也不适用,因为它似乎需要 两次 来回转换。哦,GCC 给你一个warning 关于我在我的回答中写的内容:) Clang 出于某种原因接受了代码而不发出警告并打印三个零。涉及 UB 时意义不大。
  • 嗯,我一度认为void* 也是一个对象指针(与函数指针相对)。那么这意味着您根本无法在void* 和函数指针之间进行转换? /9 可以应用(除了转换问题),因为Test() 返回一个空指针值(“空指针常量可以转换为指针类型;结果是 空指针值” )
  • void* 的演员是红鲱鱼。 The problem remains 如果使 Test 返回一个 NULL 函数指针,并且看不到强制转换。
  • 哦,我确实是正确的假设 void* 是一个对象指针类型:[basic.compound]/3
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-12-28
  • 2011-03-06
  • 2020-06-11
  • 2013-03-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多