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

2022-03-24 17:43:39 +08:00
 YUCOAT

我这里有一个对象名叫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 编译标准

2418 次点击
所在节点    程序员
10 条回复
ysc3839
2022-03-24 18:06:56 +08:00
我觉得是因为 std::function 内部会拷贝
改成这样是没问题的:
```
auto f2 = [s = std::move(s)]() mutable {
SampleFunction(0, std::move(s));
};
```
把 auto 换成 std::function<void()> 后就会因为使用了 Sample(const Sample&) 而出错
statumer
2022-03-24 18:18:37 +08:00
std::bind 会把你的右值引用存为左值(一个成员变量),但是左值无法被 SampleFunction 接收,所以你这个 bind 是无效的,完全无法被调用。
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
2022-03-24 19:15:34 +08:00
@darklights
应该是 2 楼说的原因, bind 把你的参数存成了左值(类型还是右值引用, 这两个不一样), 左值是不会调用 move 构造的, 所以有问题.

你改之后的版本没问题是因为是没用 bind, 且使用了 std::move 将参数变成了右值↓↓↓
void operator()() { SampleFunction(0, std::move(_target)); }
};
yujincheng08
2022-03-24 19:23:07 +08:00
目测是以下问题:
`std::function` 要求可以复制,如果把 `Sample` 对象放进去了,那 `std::function` 就无法复制了。
要么让 `Sample` 可复制,要么用 `std::move_only_function`
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
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
2022-03-25 09:45:05 +08:00
@darklights 那用什么替代呢
codehero
2022-03-25 11:02:59 +08:00
@darklights
不好意思, 把你看成题主了, 所以解释了一下原因

还有, 没说让用 bind, 只是说 bind 是报错的其中一个原因
icylogic
2022-03-25 11:36:14 +08:00
@YUCOAT C++14 以后大部分场景就可以用 lambda 替代了

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/842671

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX