3

鉴于以下不可复制的任务类和示例代码

#include <functional>
#include <iostream>
#include <string>

class Task
{
public:
    Task()
    {
    }

    Task(const Task& other) = delete;
    Task& operator=(const Task& other) = delete;

    Task(Task&& other) = default;
    Task& operator=(Task&& other) = default;

    void operator()() const
    {
        std::cout << "Task !" << std::endl;
    }
};


int main()
{  
    auto task = Task();

    auto lambda = [task = std::move(task)]
    {
        task();
    };

    std::function<void()> test = std::move(lambda);

    test();
}

如果我用auto类型而不是std::function声明测试变量,则程序编译并运行完美,否则它将拒绝编译并出现此错误:

functional:1878:34: error: use of deleted function 'main()::<lambda()>::<lambda>(const main()::<lambda()>&)'
    __dest._M_access<_Functor*>() =
                                  ^
31:42: note: 'main()::<lambda()>::<lambda>(const main()::<lambda()>&)' is implicitly deleted because the default definition would be ill-formed:
31:42: error: use of deleted function 'Task::Task(const Task&)'
13:5: note: declared here

我真的需要声明测试的类型,因为它最终将成为另一个类的成员。

我怎么做 ?

我是否正确假设 std::function 应该以某种方式声明为 mutable ?

4

2 回答 2

4

decltype(foo)当您想引用 的类型时,可以使用作为类型foo。所以,你可以这样做:

decltype(lambda) test = std::move(lambda);

但是,您的既定目标是将其用作班级成员。在这种情况下,您需要一些东西来“窃取”该类型。请注意,编译器没有义务(据我所知)统一两个相同 lambda 表达式的类型。这意味着类型和 lambda 创建都必须取自同一个 lambda 表达式。

如果您真的想使用 lambdas 执行此操作并且您可以访问 C++14(用于推断的返回类型),那么您可以执行以下操作:

auto make_task_runner(Task task) {
    return [task = std::move(task)]() { task(); };
}

这给了我们一个函数,我们可以使用它来创建 lambdas 和窃取类型(通过使用decltype()函数的调用)。

然后,在您的课堂上,您可以:

class SomeClass {
    // Type alias just to make things simpler.
    using task_runner_t = decltype(make_task_runner(std::declval<Task>()));

    task_runner_t task_runner;
}

然后,您可以使用以下make_task_runner函数分配给该数据成员:

task_runner = make_task_runner(std::move(some_task));

但是,此时您已经失去了 lambda 的主要好处:动态创建新的短期、未命名函数的能力。现在我们有一个命名函数来创建 lambda 对象,并且我们已经给 lambda 类型一个名称 ( task_runner_t),那么使用 lambda 来解决这个问题还有什么意义呢?

在这种特殊情况下,自定义函子(如保罗的回答)更有意义。

...但是,Task 已经是一个仿函数,所以你已经有了你需要的类型:Task!只需使用它,而不是发明一个没有明显好处的包装器。

于 2020-06-05T18:01:12.823 回答
2

解决此问题的一种方法是放弃 lambda 为您提供的语法糖,而是使用仿函数自己完成,例如:

#include <functional>
#include <iostream>
#include <string>

class Task
{
public:
    Task()
    {
    }

    Task(const Task& other) = delete;
    Task& operator=(const Task& other) = delete;

    Task(Task&& other) = default;
    Task& operator=(Task&& other) = default;

    void operator()() const
    {
        std::cout << "Task !" << std::endl;
    }
};

class pseudo_lambda
{
public:
    pseudo_lambda (Task &&task) { m_task = std::move (task); }  // <- capture
    void operator()() const { m_task (); }                      // <- invoke
private:
    Task m_task;                                                // <- captured variable(s)
};

int main()
{  
    auto task = Task();
    pseudo_lambda pl { std::move (task) };
    pl ();
}

现场演示

于 2020-06-05T17:56:53.953 回答