使用std::function时出现奇怪的行为

4

我正在使用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(参见演示)。有人能解释一下为什么布尔运算符在包含null、不可调用的函数指针时返回true吗?请同时提供一种检查std::function中的nullptr的解决方法。
注意:我已经尝试过检查目标是否为空(使用foo.target<void*>() == nullptr),而不是使用布尔运算符,但似乎无论函数对象包含什么内容,目标始终为空(即使函数对象可以完全正常地被调用)。

clang++3.4 输出0 0 0 - dyp
这让我感到很熟悉,实际上,它可能与以下内容有关:https://dev59.com/mGQn5IYBdhLWcg3wcm17(除了UB问题,请参见Daniel Frey的答案) - dyp
2个回答

5

这看起来像是一个错误。首先,这里有一个不使用转型玩任何游戏的简化示例

#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 function(F f); template function(allocator_arg_t, const A& a, F f);
7 需要:F 必须是可复制构造的,f必须是 Callable(20.8.11.2),以参数类型ArgTypes和返回类型R。A的复制构造函数和析构函数不得抛出异常。
8 后置条件:如果以下任一情况成立,则!*this: - f是空指针函数。 - f是空指针成员。 - F是函数类模板的实例,并且!f。

1
请参见我的评论中链接的问题,转到http://gcc.gnu.org/bugzilla/show_bug.cgi?id=57465。 - dyp

4

我觉得代码并没有做你认为的那样。这一行:

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

这意味着你从`Test()`接收到一个`void*`。然后你尝试将这个指向对象的指针强制转换为指向函数的指针。这是不允许的,因此代码会产生未定义的行为,因此编译器的任何输出都是有效的。
标准中相关的部分如下:
5.2.10 Reinterpret cast [expr.reinterpret.cast]
8.将函数指针转换为对象指针类型或反之亦然是有条件支持的。这种转换的含义是实现定义的,除非实现支持双向转换,将一个类型的prvalue转换为另一个类型并返回,可能具有不同的cv资格,应产生原始指针值。
9.空指针值(4.10)被转换为目标类型的空指针值。[注意:std :: nullptr_t类型的空指针常量不能转换为指针类型,并且整数类型的空指针常量不一定转换为空指针值。-结束注释]

4.10 Pointer conversions [conv.ptr]
1.空指针常量是一个整数类型的积极常量表达式(5.19),其计算结果为零或类型为std :: nullptr_t的prvalue。空指针常量可以转换为指针类型;结果是该类型的空指针值,并且与对象指针或函数指针类型的每个其他值都是可区分的。
这是一个简化的测试用例,将`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


我不确定这是否是未定义行为,请参见[expr.reinterpret.cast] /8+9(第8条规定它是实现定义的,但通常规则是nullptr值-> nullptr值)。 - dyp
@DyP /9 不适用,据我所知,Test() 返回的是 void*,而不是 0std::nullptr_t。查看 /8,我认为它也不适用,因为它似乎需要 两个 转换,来回转换。哦,还有 GCC 给你一个警告,关于我在答案中写的内容 :) Clang 出于某种原因接受代码而没有警告,并打印出三个零。当涉及 UB 时,这意义不大。 - Daniel Frey
嗯,我曾经认为void*也是一个对象指针(与函数指针相对),但这只是一瞬间的想法。那么这是否意味着你根本无法在void*和函数指针之间进行转换?/9可能适用(除了转换问题),因为Test()返回一个空指针值(“空指针常量可以转换为指针类型;结果是空指针值”)。 - dyp
void*的转换是一个误导。即使没有强制转换,如果Test返回一个NULL函数指针,问题仍然存在(http://ideone.com/0U1ZGJ)。 - Igor Tandetnik
哦,我确实正确地假设void*是一个对象指针类型:[basic.compound]/3。 - dyp
显示剩余6条评论

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