为啥 C++11 不能将不可复制的仿函数移动到 std::function?

Posted

技术标签:

【中文标题】为啥 C++11 不能将不可复制的仿函数移动到 std::function?【英文标题】:Why can't C++11 move a noncopyable functor to a std::function?为什么 C++11 不能将不可复制的仿函数移动到 std::function? 【发布时间】:2012-07-30 21:10:00 【问题描述】:
//------------------------------------------------------------------------------
struct A

    A()
    A(A&&)
    A& operator=(A&&)return *this;
    void operator()()

private:
    A(const A&);
    A& operator=(const A&);

    int x;
;

//------------------------------------------------------------------------------
int main()

    A a;
    std::function<void()> func(std::move(a));

'A::A' : 无法访问在类 'A' 中声明的私有成员

似乎当我通过引用或const 捕获某些东西时,我可以制作一个不可复制的 lambda。但是,当我这样做时,它实际上可以将其提供给std::function

【问题讨论】:

作为workaround,您可以在 std::function 和仿函数之间放置一个可复制的适配器。适配器有一个虚拟(复制时移动)复制构造函数,如果复制则抛出。 【参考方案1】:

简短的回答是 C++11 规范要求您的 A 必须是 CopyConstructible 才能与 std::function 一起使用。

很长的答案是存在这个要求,因为std::function 在构造函数中删除了仿函数的类型。为此,std::function 必须通过虚函数访问函子的某些成员。其中包括调用运算符、复制构造函数和析构函数。由于这些是通过虚拟调用访问的,因此无论您是否实际使用std::function 的复制构造函数、析构函数或调用运算符,它们都被“使用”了。

【讨论】:

如果我错了(可能我错了),请纠正我,但是,为了使用虚函数,你需要一个 vtable,因此需要一个基类。在这种情况下,哪个是基类? 请参阅***.com/questions/6324694/…,了解类型擦除的工作原理。基类是 std::function 的实现细节。派生类也是一个实现细节,但也是在提供的仿函数上模板化的。 @HowardHinnant:你认为如果有人提出修改以细化要求,那么它会通过每个操作吗? (如果 std::function 被复制,则仅要求 A 是可复制构造的。) @GManNickG:它唯一能通过的机会是,如果有一个随附的实现清楚地证明它可以通过所有现有的单元测试并拥有足够友好的版权许可。我个人目前不知道如何创建这样的实现。但我喜欢这个行业的一点是我总是在学习新的东西。 :-) 作为一个不错的 C++14 解决方法,您可以使用来自 naios.github.io/function2 的 fu2::unique_function 类,它既是仅移动类型,也允许将不可复制的可调用对象移动到其中,这是一个典型示例这将是一个 lambda 在其上下文中移动捕获不可复制的对象【参考方案2】:

std::function 要求仿函数是可复制的。

这是一个任意的设计决定。可能的替代方案可能是:

允许使用不可复制的仿函数,但尝试复制生成的 std::function 会导致崩溃/异常。 允许使用不可复制的函子,std::functions 本身始终不可复制。 ...

如果生成的 std::function 被复制,则不可能只要求可复制性,因为它可以在不同的 TU(翻译单元)中复制。

所以,如果你问我,当前的行为是较小的邪恶。

【讨论】:

【参考方案3】:

这是 Visual Studio 中的一个错误。它尝试省略一个副本(实际上它应该尝试省略一个移动),这需要一个可访问的复制构造函数。最好的解决方案是简单地声明复制构造函数/赋值运算符并且从不定义它们。该类仍然是不可复制的,但代码会编译,因为 VS 永远不会尝试实际调用复制构造函数。

【讨论】:

如果我按照您的建议进行操作,它显然会尝试调用复制 ctor,因为我得到了一个未解决的外部。 @Dave:是的,这就是为什么它是一个错误。标准说应该允许,但是VS做的不对。 @NicolBolas 很好,但他的解决方法建议没有奏效。我不被允许这么说? (我很感谢 DeadMG 的回答,谢谢) 您提到的错误存在(并且非常烦人)但这不是这里的问题。

以上是关于为啥 C++11 不能将不可复制的仿函数移动到 std::function?的主要内容,如果未能解决你的问题,请参考以下文章

为啥 c++ 线程是可移动的但不可复制的?

Initializer-list-构造一个不可复制(但可移动)对象的向量

使类不可复制*和*不可移动

将不可复制的闭包对象传递给 std::function 参数

为啥 C 函数不能返回数组类型? [复制]

为啥将 C-Array 传递给函数时 sizeof() 值错误? [复制]