将唯一指针传递给 lambda 捕获中的函数以进行并行执行

问题描述 投票:0回答:1

这个问题是这个问题的后续问题。值得注意的是,通过删除

<execution>
标头并使用
std::for_each()
重载而不执行策略,效果很好。

我有一个带有最小复制示例的设置,如下所示。

基类 A 是纯虚函数,两个派生类具有不同版本的函数

d()
,该函数应该执行一些操作。

我使用工厂函数

choose_operation
在运行时在类
B
C
之间进行选择,它返回一个
std::unique_ptr<A>
。然后我使用
std::for_each
,我需要将
A::d()
的正确版本传递到
std::for_each
,然后从那里传递到执行更多工作的函数
do_stuff()
。无论我使用
std::execution::seq
还是
std::execution::par
,错误都是一样的。

#include <algorithm>
#include <iostream>
#include <vector>
#include <memory>
#include <cmath>
#include <execution>

struct A {
    A() {};
    virtual ~A() = default;
    virtual double d(double x) = 0;
};

struct B : A {
    double d(double x) override { return std::sqrt(std::abs(x)); }
};

struct C : A {
    double d(double x) override { return std::abs(x); }
};

std::unique_ptr<A> choose_operation(std::string s) {
    if (s == "B") {
        return std::make_unique<B>();
    } else {
        return std::make_unique<C>();
    }
}

double do_stuff(double x, const std::unique_ptr<A>& dfun) {
    x -= 2;
    return dfun->d(x);
}

int main() {
    std::vector<double> v {3, -4, 2, -8, 15, 267};
    auto dfun = choose_operation("B");

    std::for_each(std::execution::seq, v.begin(), v.end(), [dfun = std::move(dfun)](double& x) {
        x = do_stuff(x, dfun);
    });

    return 0;
}

我用

g++-13 -std=c++23 main.cpp
编译它。

这是错误消息:

In file included from /opt/homebrew/Cellar/gcc/13.2.0/include/c++/13/execution:40,
                 from main.cpp:6:
/opt/homebrew/Cellar/gcc/13.2.0/include/c++/13/pstl/glue_algorithm_impl.h: In instantiation of '__pstl::__internal::__enable_if_execution_policy<_ExecutionPolicy, void> std::for_each(_ExecutionPolicy&&, _ForwardIterator, _ForwardIterator, _Function) [with _ExecutionPolicy = const __pstl::execution::v1::sequenced_policy&; _ForwardIterator = __gnu_cxx::__normal_iterator<double*, vector<double> >; _Function = main()::<lambda(double&)>; __pstl::__internal::__enable_if_execution_policy<_ExecutionPolicy, void> = void]':
main.cpp:39:18:   required from here
/opt/homebrew/Cellar/gcc/13.2.0/include/c++/13/pstl/glue_algorithm_impl.h:59:40: error: use of deleted function 'main()::<lambda(double&)>::<lambda>(const main()::<lambda(double&)>&)'
   59 |     __pstl::__internal::__pattern_walk1(
      |     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^
   60 |         std::forward<_ExecutionPolicy>(__exec), __first, __last, __f,
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   61 |         __pstl::__internal::__is_vectorization_preferred<_ExecutionPolicy, _ForwardIterator>(__exec),
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   62 |         __pstl::__internal::__is_parallelization_preferred<_ExecutionPolicy, _ForwardIterator>(__exec));
      |         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
main.cpp:39:83: note: 'main()::<lambda(double&)>::<lambda>(const main()::<lambda(double&)>&)' is implicitly deleted because the default definition would be ill-formed:
   39 |     std::for_each(std::execution::seq, v.begin(), v.end(), [dfun = std::move(dfun)](double& x) {
      |                                                                                   ^
main.cpp:39:83: error: use of deleted function 'std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = A; _Dp = std::default_delete<A>]'
In file included from /opt/homebrew/Cellar/gcc/13.2.0/include/c++/13/memory:78,
                 from main.cpp:4:
/opt/homebrew/Cellar/gcc/13.2.0/include/c++/13/bits/unique_ptr.h:522:7: note: declared here
  522 |       unique_ptr(const unique_ptr&) = delete;
      |       ^~~~~~~~~~
In file included from /opt/homebrew/Cellar/gcc/13.2.0/include/c++/13/pstl/glue_execution_defs.h:50,
                 from /opt/homebrew/Cellar/gcc/13.2.0/include/c++/13/execution:34:
/opt/homebrew/Cellar/gcc/13.2.0/include/c++/13/pstl/algorithm_impl.h:107:98: note:   initializing argument 4 of 'void __pstl::__internal::__pattern_walk1(_ExecutionPolicy&&, _ForwardIterator, _ForwardIterator, _Function, _IsVector, std::false_type) [with _ExecutionPolicy = const __pstl::execution::v1::sequenced_policy&; _ForwardIterator = __gnu_cxx::__normal_iterator<double*, std::vector<double> >; _Function = main()::<lambda(double&)>; _IsVector = std::integral_constant<bool, false>; std::false_type = std::integral_constant<bool, false>]'
  107 | __pattern_walk1(_ExecutionPolicy&&, _ForwardIterator __first, _ForwardIterator __last, _Function __f,
      |                          

有人可以帮助解释错误消息,并建议将唯一指针传递到 lambda 捕获的适当方法吗?

我还应该补充一点,对象

dfun
在代码中的其他地方使用,因此简单地将其定义放在 lambda 体内并不是一个好的解决方案。

后续问题

我注意到 for 循环的以下版本确实可以编译,但是在这里传递引用是一个好的做法吗?

std::for_each(std::execution::seq, v.begin(), v.end(), [dfun = &dfun](double& x) {
        x = do_stuff(x, *dfun);
    });
c++ lambda stl c++17
1个回答
0
投票

并行算法尝试复制 lambda,但这不起作用,因为

unique_ptr
只能移动。

C++ 标准部分“算法要求”

[algorithms.requirements]
第 10 段:

除非另有说明,以函数对象作为参数的算法允许自由复制这些函数对象。对于对象标识很重要的程序员来说,应该考虑使用指向非复制实现对象的包装类,例如

reference_wrapper<T>
(20.14.5),或一些等效的解决方案

简而言之:假设您可以将仅移动函子传递给

<algorithm>
库中的任何内容都是不安全的。通常它是有效的,因为为什么基本算法应该复制它的函子,对吗?

但是,使用并行算法,事情就变得更加复杂。此外,作为一名程序员,您实际上经常希望拥有一个副本,以便可以将线程本地状态(例如分配)放入函子中,并在调用之间重用它们。遗憾的是,该标准使得这几乎不可能,因为它没有指定是否会进行复制。例如,请参阅使用并行算法时的线程特定变量

无论如何,请尝试

std::ref
或切换到
shared_ptr

© www.soinside.com 2019 - 2024. All rights reserved.