这个问题是这个问题的后续问题。值得注意的是,通过删除
<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);
});
并行算法尝试复制 lambda,但这不起作用,因为
unique_ptr
只能移动。
C++ 标准部分“算法要求”
[algorithms.requirements]
第 10 段:
除非另有说明,以函数对象作为参数的算法允许自由复制这些函数对象。对于对象标识很重要的程序员来说,应该考虑使用指向非复制实现对象的包装类,例如
(20.14.5),或一些等效的解决方案reference_wrapper<T>
简而言之:假设您可以将仅移动函子传递给
<algorithm>
库中的任何内容都是不安全的。通常它是有效的,因为为什么基本算法应该复制它的函子,对吗?
但是,使用并行算法,事情就变得更加复杂。此外,作为一名程序员,您实际上经常希望拥有一个副本,以便可以将线程本地状态(例如分配)放入函子中,并在调用之间重用它们。遗憾的是,该标准使得这几乎不可能,因为它没有指定是否会进行复制。例如,请参阅使用并行算法时的线程特定变量
无论如何,请尝试
std::ref
或切换到shared_ptr
。