【问题标题】:Should std::function::operator bool return false after move? [duplicate]移动后std::function::operator bool是否应该返回false? [复制]
【发布时间】:2015-01-21 23:25:32
【问题描述】:

在 C++11 中,std::functionMoveConstructible,即可以在此类对象上有意义地调用 std::move 或将它们存储在可移动类型中。一个困惑:下面的代码应该打印什么?

#include <stdio.h>

#include <functional>
#include <utility>

struct Big {
    char data[1024];
};

int main(int argc, char **argv) {
    Big blob;
    // This bind will trigger small object optimization
    std::function<void()> little = std::bind([]() { printf("little\n"); });
    // This bind will not
    std::function<void()> big = std::bind([](Big const& b) {
            printf("big %c\n", b.data[0]);
        }, blob);
    auto little_moved = std::move(little);
    auto big_moved = std::move(big);

    // After move, one expects the source std::function to be empty
    // (boolean value false)

    printf("Little empty: %d\n", !little);
    printf("Little (moved) empty: %d\n", !little_moved);
    printf("Big empty: %d\n", !big);
    printf("Big (moved) empty: %d\n", !big_moved);

    return 0;
}

使用 GCC 4.8 编译,您会得到:

linux-dev:nater:/tmp$ g++-4.8 -g -o foo move_function.cc  -std=c++11
linux-dev:nater:/tmp$ ./foo
Little empty: 1
Little (moved) empty: 0
Big empty: 1
Big (moved) empty: 0

对象按预期运行,使移动分配的 RHS 无效。但是,clang(Apple LLVM 6.0 版)并不是很清楚:

workbrick:nater:/tmp$ clang++ -g -o foo move_function.cc -std=c++11 -stdlib=libc++
workbrick:nater:/tmp$ ./foo
Little empty: 0
Little (moved) empty: 0
Big empty: 1
Big (moved) empty: 0

这里,当绑定参数很大时,RHS 在移动后无效(在布尔上下文中为 false),但当绑定参数很小(技术上不存在)时则不会。检查 Xcode 附带的 &lt;functional&gt; 的实现,我们发现行为会有所不同,具体取决于是否应用了 小对象优化

template<class _Rp, class ..._ArgTypes>
template <class _Alloc>
function<_Rp(_ArgTypes...)>::function(allocator_arg_t, const _Alloc&,
                                     function&& __f)
{
    if (__f.__f_ == 0)
        __f_ = 0;
    else if (__f.__f_ == (__base*)&__f.__buf_)
    {
        // [nater] in this optimization, __f.__f_ is not invalidate
        __f_ = (__base*)&__buf_;
        __f.__f_->__clone(__f_);
    }
    else
    {
        // [nater] here, the RHS gets invalidated
        __f_ = __f.__f_;
        __f.__f_ = 0;
    }
}

现在,我知道移动分配后 RHS 的状态是特定于类型的,但我惊讶这个 Standard 类的行为不一致。这在规范中真的没有定义吗?

【问题讨论】:

  • 确实如此。对于后代:(6) 效果:如果 !f,*this 没有目标;否则,将 f 的目标移动构造成 *this 的目标,使 f 处于未指定值的有效状态。 随意将其提升为答案,我会接受它。

标签: c++ c++11 gcc clang move-semantics


【解决方案1】:

dyp 的helpful comment 对此感到羞愧,在!func 之后!func 是否为真(或对象上的任何其他移动)确实是未定义的行为。 C++11 规范中的相关文本:

(6) 效果:如果 !f,*this 没有目标;否则,将 f 的目标移动构造到 *this 的目标中,使 f 处于未指定值的有效状态

undefined 未指定行为的又一次胜利。

【讨论】:

  • 在这种情况下看到术语“未定义的行为”有点奇怪。是的,如果函数对象为空,则它是“未定义”,但移动构造函数不会调用未定义的行为(以这种方式)。 (UB 给了做任何事情的余地,而这里可能的行为受到“有效状态”的限制。)
  • 好吧,即使对象的状态是“有效”,从 std::function 移出的语义是未定义的,不是吗?也就是说,标准中的语言允许库实现者调用operator booloperator() 具有它们的任何(有效)效果,并且在clang 的实现的情况下观察到两种行为,具体取决于包装好的Callable。移出对象在一种情况下不是空的,而在另一种情况下是空的。也许我们需要一个新的类别来处理不是“未定义”但仍然不可能知道的行为;)
  • “未定义行为”和“未指定行为”之间的细微差别在于后者“通常由本 [C++] 国际标准描述”[defns.unspecified]。术语“未定义行为”表明operator bool 可能会抛出异常或擦除硬盘驱动器以符合(但愚蠢)的实现。
【解决方案2】:

未指定对象在移动后具有什么价值。

当然,除了它是一些有效值。

有几种常见的情况:

  1. 如果复制便宜且不能扔,则源未被触及。
  2. 源处于默认构造状态。
  3. 源保留了目标之前的任何值。 (它被交换了。)
  4. 源处于某种瘫痪状态,只能安全地销毁和分配。

通常,选择最有用的最便宜的替代品。

【讨论】:

    猜你喜欢
    • 2015-05-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-04-24
    • 2014-01-11
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多