【问题标题】:No suitable copy constructor after capturing a class with lambda使用 lambda 捕获类后没有合适的复制构造函数
【发布时间】:2020-04-23 19:47:32
【问题描述】:

我有以下代码:

#include <iostream>

class Foo
{public:
    Foo() {}
    int a;
};

int main()
{
    Foo foo;

    auto lambda = [=]() mutable { std::cout << foo.a; };

}

一切正常,直到我需要向我的 Foo 类添加一个复制构造函数:

Foo(Foo& t) {}

它不再编译,给出消息:

类'Foo':没有可用的复制构造函数或复制构造函数是 声明为“显式”

我使 lambda 可变,因为我不想捕获 const Foo,但我认为正在发生的事情是无法复制 lambda。另一个编译器有一个更有用的错误信息:

错误:使用已删除的功能 ‘main()::::(main()::&&)'

和:

main()::::(main()::&&)' 是隐式的 删除,因为默认定义格式不正确:

但我真的不明白这一点。那个隐式删除的函数是 lambda 的移动构造函数吗?我不明白为什么只是将复制构造函数添加到捕获的类(而不是 lambda)会发生这种情况。

这就是我想象的 lambda/functor 的样子:

class lambda
{public:

     Foo foo; // <---- My captured variable/class
     void operator()(){ std::cout << foo.a; }
}

那么将这些 lambda 中的一个复制到另一个涉及到调用 Foo 的赋值运算符或复制构造函数?我不明白 Foo 有一个复制构造函数是如何使这个失败或者什么是“格式错误”。我注意到的另一件事是,当 lambda 通过引用 [&] 捕获时没有问题。

编辑:它不能在这个编译器上编译:

https://www.jdoodle.com/online-compiler-c++/

我在 Visual Studio 上,但它无法编译。然而,当我做了一个小得多的例子时,它会编译,但仍然强调错误。在我较大的项目中,它无法编译。

【问题讨论】:

  • 您的代码(带有添加的“复制构造函数”)编译时无需窥视 MSVCclang-cl(使用 C++17 标准)。您使用的是什么编译器和标准?

标签: c++ lambda copy-constructor


【解决方案1】:

copy constructor 原型是 A(const A&amp;)。您实际上错过了复制构造函数上的 const 限定符,这就是发生错误的原因。

【讨论】:

  • 我一直在阅读这个,复制构造函数可以是 const 也可以不是,我很确定它必须是非常量的,如果它拥有指针或其他东西的所有权。 en.wikipedia.org/wiki/Copy_constructor_(C++) 编辑:在我的 Foo 复制构造函数中,我修改了另一个 Foo,我复制了指针并将另一个设置为 nullptr。
  • @Zebrafish,复制 ctor 应该采用 const 引用。它的工作是复制,它应该修改原始对象。
  • @Evg 如何复制另一个 Foo 的指针并将其设置为 nullptr(如果它是 const)?
  • @Zebrafish“我复制指针并将另一个设置为 nullptr”听起来更像是移动而不是复制。
  • @Zebrafish,如果要修改原始对象中的指针,请使用移动构造函数,该构造函数接受右值引用 (Obj&amp;&amp;)。
【解决方案2】:
auto lambda = [=]() mutable { std::cout << foo.a; };

在右侧创建临时关闭。基于这个临时闭包,通过调用编译器生成的默认移动构造函数来构造另一个。

closure c(closure{});

move 构造函数的默认实现只是将所有数据成员一一移动:

struct closure {
    Foo foo;

    closure (closure&& theOther) : foo(std::move(theOther.foo))   // <--- [1]
    {}                              // binding rvalue ref to lvalue ref
};

您的Foo ctor 采用Foo&amp;,但不允许将 rvalue 引用绑定到 lvalue 引用。

它可以在 MSVC 中工作,因为它具有处理此类事情的扩展。在 G++/Clang 下它一定会失败。

使用const Foo&amp;,可以正常工作,因为临时可以绑定到 const lvalue ref。

【讨论】:

  • 哇,那幅插图对我很有帮助。我有一个包含指针的类,当我将一个分配给另一个时,新的应该拥有指针的所有权。但是如果我制作移动构造函数 Foo(Foo&& other) 那么我不能去 Foo foo1;富 foo2 = foo1;因为 foo1 不是右值。我有一个返回 Foo 的函数,例如 Foo foo = createNewFoo();我应该从那个函数返回什么?
  • 在第一种情况下,为什么不使用Foo foo2(std::move(foo1));。关于第二个问题,函数只是返回Foo,新的实例将由移动构造函数构造,然后它取得指针的所有权。
  • 如果我没记错的话,C++ 标准版本在这里起主要作用。在 C++17 下代码有效,在 C++11/14 下无效。我在回答中给出了解释。
【解决方案3】:

要获得编译错误,您应该在 C++11 或 C++14 标准下编译此代码。在 C++17 中它是有效的。 Demo.

让我们考虑

struct Foo {
    Foo() {}
    Foo(Foo&) {}
};

在C++17中我们可以写

auto f = Foo{};

但在 C++11/14 中,这一行将无法编译。原因是在 C++17 中我们有mandatory copy elision,并且复制构造函数调用的正确性,因为Foo&amp; 不能绑定到临时的Foo{}Foo(Foo&amp;&amp;) 被删除,所以不是甚至被编译器检查过。

这直接转换为 lambda(rafix07 的答案解释了如何),因为它按值捕获 Foo。 lambda 本身很好。例如,你可以写

[=] { std::cout << foo.a; };

但是 lambda 的 move 构造函数格式不正确,在 C++11/14 中它必须为该行格式正确

auto lambda = [=] { std::cout << foo.a; };

编译。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-08-20
    • 1970-01-01
    • 2023-03-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-04-18
    相关资源
    最近更新 更多