V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
YUCOAT
V2EX  ›  程序员

请问这段 C++代码为什么会编译不过

  •  
  •   YUCOAT · 2022-03-24 17:43:39 +08:00 · 2378 次点击
    这是一个创建于 976 天前的主题,其中的信息可能已经有所发展或是发生改变。

    我这里有一个对象名叫Sample,它不支持拷贝赋值与拷贝构造,但是支持转移赋值和转移构造

    现在我需要创建一个std::function<void()>函数对象,其中的参数就有Sample对象, 我的代码是这样写的:

    #include <functional>
    
    struct Sample {
        Sample(Sample&& p) {
            m_pData = p.m_pData;
            p.m_pData = nullptr;
        };
    
        static Sample Create(void* data) {
            return { data };
        }
    
        Sample& operator=(Sample&& p)
        {
            m_pData = p.m_pData;
            p.m_pData = nullptr;
            return *this;
        }
    
    private:
        Sample(void* data) { m_pData = data; }
    
    private:
        Sample(const Sample&) = delete;
        Sample& operator=(const Sample&) = delete;
    
        void* m_pData;
    };
    
    void SampleFunction(int i, Sample f)
    {
    
    }
    
    int main()
    {
        Sample s = Sample::Create(nullptr);
        std::function<void()> f2 = std::bind(SampleFunction, 0, std::move(s));
        return 0;
    }
    

    结果编译出错了,错误信息是:

    error C2440: “初始化”: 无法从“std::_Binder<std::_Unforced,void (__cdecl &)(int,Sample),int,Sample>”转换为“std::function<void (void)>”
    
    

    请问这是什么原因?以及怎么改正? 编译环境是 Windows + vs2015 C++14 编译标准

    第 1 条附言  ·  2022-03-25 09:43:43 +08:00
    感谢大家的回答,我明白了。意思就是 std::function 它是需要支持拷贝的,而我的 Sample 不允许拷贝和赋值,所以我的代码编译不过。

    stl 这个地方设计得还是有点坑,这一点 Chrome 中的 base::Callback 设计得比较好,没有这个问题。
    10 条回复    2022-03-25 11:36:14 +08:00
    ysc3839
        1
    ysc3839  
       2022-03-24 18:06:56 +08:00   ❤️ 1
    我觉得是因为 std::function 内部会拷贝
    改成这样是没问题的:
    ```
    auto f2 = [s = std::move(s)]() mutable {
    SampleFunction(0, std::move(s));
    };
    ```
    把 auto 换成 std::function<void()> 后就会因为使用了 Sample(const Sample&) 而出错
    statumer
        2
    statumer  
       2022-03-24 18:18:37 +08:00 via iPhone   ❤️ 3
    std::bind 会把你的右值引用存为左值(一个成员变量),但是左值无法被 SampleFunction 接收,所以你这个 bind 是无效的,完全无法被调用。
    darklights
        3
    darklights  
       2022-03-24 18:46:19 +08:00
    void SampleFunction(int, Sample)
    {
    }

    struct SampleFunctor
    {
    mutable Sample _target;

    SampleFunctor(Sample&& target) : _target(std::move(target)) {}

    SampleFunctor(const SampleFunctor& rhs) : _target(std::move(rhs._target)) {}
    SampleFunctor(SampleFunctor&& rhs) : _target(std::move(rhs._target)) {}

    void operator()() { SampleFunction(0, std::move(_target)); }
    };

    int main()
    {
    Sample s = Sample::Create(nullptr);
    std::function<void()> f { SampleFunctor{ std::move(s) } };
    return 0;
    }

    ~~~~~~~~

    经 1 楼启发,改成以上能通过编译。因为 std::function 是可复制的,所以它的 target 也必须是可复制,应该是属于那种“就算用不到但必须提供”。
    From cppreference:std::decay<F>::type must meet the requirements of Callable and CopyConstructible.

    以上这代码非常危险,切勿模仿。
    codehero
        4
    codehero  
       2022-03-24 19:15:34 +08:00
    @darklights
    应该是 2 楼说的原因, bind 把你的参数存成了左值(类型还是右值引用, 这两个不一样), 左值是不会调用 move 构造的, 所以有问题.

    你改之后的版本没问题是因为是没用 bind, 且使用了 std::move 将参数变成了右值↓↓↓
    void operator()() { SampleFunction(0, std::move(_target)); }
    };
    yujincheng08
        5
    yujincheng08  
       2022-03-24 19:23:07 +08:00
    目测是以下问题:
    `std::function` 要求可以复制,如果把 `Sample` 对象放进去了,那 `std::function` 就无法复制了。
    要么让 `Sample` 可复制,要么用 `std::move_only_function`
    darklights
        6
    darklights  
       2022-03-24 19:38:56 +08:00
    @codehero

    我回答的是“以及怎么改正”。

    一开始我试的就是 1 楼说的:std::function<void()> f2 = [s = std::move(s)]() mutable { SampleFunction(0, std::move(s)); };
    照样炸。只是当时没明白为何炸。

    2022 没人用 bind 了。
    FrankHB
        7
    FrankHB  
       2022-03-24 23:54:44 +08:00
    @darklights 只有 C++11 能用,不用 bind 怎么模拟 capture-init ?自己写?
    也不是没理由自己造,比如 wg21.link/p0826 。但是这种问题遇到的概率比你 function 的坑小得多,类似的逻辑就更没理由去用 std::function 。
    作为通用目的的 call wrapper ,std::function 的 CopyConstructible 要求本来就是个二缺设计,跟核心语言的 copy initialization 要求格格不入。最欠的是这种要求直接写在了 ctor template 里,所以用户能保证自己不 copy 也会被坑。
    反正我是选择自己糊:
    github.com/FrankHB/YSLib/blob/master/YBase/include/ystdex/function.hpp
    正好干掉不支持分配器、不支持定制空调用行为和某些实现依赖 RTTI 的代码膨胀之类乱七八糟的屑问题。
    YUCOAT
        8
    YUCOAT  
    OP
       2022-03-25 09:45:05 +08:00
    @darklights 那用什么替代呢
    codehero
        9
    codehero  
       2022-03-25 11:02:59 +08:00
    @darklights
    不好意思, 把你看成题主了, 所以解释了一下原因

    还有, 没说让用 bind, 只是说 bind 是报错的其中一个原因
    icylogic
        10
    icylogic  
       2022-03-25 11:36:14 +08:00
    @YUCOAT C++14 以后大部分场景就可以用 lambda 替代了
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5171 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 01:24 · PVG 09:24 · LAX 17:24 · JFK 20:24
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.