You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何Clang实现std::function移动语义时会复制小对象?

libc++中std::function小对象优化时移动操作执行复制的原因分析

在libc++的std::function实现中,当被类型擦除的函数对象体积足够小、可以放入*SBO(小对象优化)*空间时,移动操作会执行复制而非移动。但并非所有栈内存占用小的对象都适合复制,为什么要选择复制而不是移动?

测试现象

  • 使用Clang测试时,test1()的语义与显式复制的test3()完全一致,通过shared_ptr的引用计数变化可以验证这一点:移动操作后引用计数显著增加,说明底层对象被复制了。
  • 使用GCC测试时行为符合预期:移动操作不会增加引用计数,底层对象被正确移动。

标准与实现的矛盾

两种实现均符合C标准要求——std::function要求存储的函数对象必须可复制,且移动后的原对象处于未指定状态。但libc的这种设计让人疑惑:类似逻辑如果套用到std::map上,假设键值都可复制,难道移动std::map时也要生成新副本?这显然不符合移动语义的初衷。而且根据cppreference的说明,此处应当执行移动操作。

示例代码

#include <iostream>
#include <memory>
#include <functional>
#include <array>
#include <type_traits>

void test1()
{
    /// 用shared_ptr观察内部行为,它的use_count()能直观反映对象的复制/移动情况
    auto foo = std::make_shared<int>(0);

    /// shared_ptr并非平凡可复制类型,复制可能带来额外开销
    /// C++23之前没有move_only_function,只能用std::function
    static_assert(!std::is_trivially_copyable_v<std::decay_t<decltype(foo)>>);
    static_assert(!std::is_trivially_copy_constructible_v<std::decay_t<decltype(foo)>>);

    std::cout << std::endl;
    std::cout << "Test 1: 可放入SBO的小型函数对象" << std::endl;
    std::cout << "引用计数: " << foo.use_count() << std::endl;
    
    std::function<void()> f = [foo] {
        /// 空操作,仅捕获对象
    };

    std::cout << "引用计数: " << foo.use_count() << std::endl;

    {
        auto g = std::move(f);

        /// 底层被类型擦除的数据实际是被复制而非移动
        std::cout << "引用计数: " << foo.use_count() << std::endl;
    }

    std::cout << "引用计数: " << foo.use_count() << std::endl;
}

void test2()
{
    auto foo = std::make_shared<int>(0);

    std::cout << std::endl;
    std::cout << "Test 2: 无法放入SBO的大型函数对象" << std::endl;
    std::cout << "引用计数: " << foo.use_count() << std::endl;
    
    std::function<void()> f = [foo, bar = std::array<char, 1024>()] {
        (void)bar;
        /// 空操作,仅捕获对象
    };

    std::cout << "引用计数: " << foo.use_count() << std::endl;

    {
        auto g = std::move(f);

        std::cout << "引用计数: " << foo.use_count() << std::endl;
    }

    std::cout << "引用计数: " << foo.use_count() << std::endl;
}

void test3()
{
    auto foo = std::make_shared<int>(0);

    std::cout << std::endl;
    std::cout << "Test 3: 显式复制小型函数对象" << std::endl;
    std::cout << "引用计数: " << foo.use_count() << std::endl;
    
    std::function<void()> f = [foo] {
        /// 空操作,仅捕获对象
    };

    std::cout << "引用计数: " << foo.use_count() << std::endl;

    {
        auto g = f;

        std::cout << "引用计数: " << foo.use_count() << std::endl;
    }

    std::cout << "引用计数: " << foo.use_count() << std::endl;
}

int main()
{
    test1();
    test2();
    test3();
    return 0;
}

内容的提问来源于stack exchange,提问作者GreenScape

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 15:20:29