我正试图将一个函子移动到一个对象内的lambda中,如下所示:
#include <functional>
#include <iostream>
#include "boost/stacktrace.hpp"
#define fwd(o) std::forward<decltype(o)>(o)
struct CopyCounter {
CopyCounter() noexcept = default;
CopyCounter(const CopyCounter &) noexcept {
std::cout << "Copied at" << boost::stacktrace::stacktrace() << std::endl;
counter++;
}
CopyCounter(CopyCounter &&) noexcept = default;
CopyCounter &operator=(CopyCounter &&) noexcept = default;
CopyCounter &operator=(const CopyCounter &) noexcept {
std::cout << "Copied at " << boost::stacktrace::stacktrace() << std::endl;
counter++;
return *this;
}
inline static size_t counter = 0;
};
struct Argument : CopyCounter {};
struct Functor : CopyCounter {
int operator()(Argument) { return 42; }
};
template <class Result>
class Invoker {
std::function<void()> invoke_;
Result* result_ = nullptr;
template <class Functor, class... Args>
Invoker(Functor&& f, Args&&... args) {
if constexpr (std::is_same_v<Result, void>) {
invoke_ = [this, f = fwd(f), ... args = fwd(args)]() mutable {
f(fwd(args)...);
};
} else {
invoke_ = [this, f = fwd(f), ...args = fwd(args)]() mutable {
result_ = new Result(f(fwd(args)...));
};
}
}
template <class Functor, class... Args>
friend auto make_invoker(Functor&& f, Args&&... args);
public:
~Invoker() {
if (result_) delete result_;
}
};
template <class Functor, class... Args>
auto make_invoker(Functor&& f, Args&&... args) {
return Invoker<decltype(f(args...))>(fwd(f), fwd(args)...);
}
int main() {
Functor f;
Argument a;
auto i = make_invoker(std::move(f), std::move(a));
assert(CopyCounter::counter == 0);
return 0;
}有些令人惊讶的是,最后一个断言在libc++上失败了,但libstdc++失败了。堆栈跟踪提示执行的两个副本:
Copied at 0# CopyCounter at /usr/include/boost/stacktrace/stacktrace.hpp:?
1# 0x00000000004C812E at ./src/csc_cpp/move_functors.cpp:38
2# std::__1::__function::__value_func<void ()>::swap(std::__1::__function::__value_func<void ()>&) at /usr/lib/llvm-10/bin/../include/c++/v1/functional:?
3# ~__value_func at /usr/lib/llvm-10/bin/../include/c++/v1/functional:1825
4# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
5# _start in ./bin/./src/csc_cpp/move_functors
Copied at 0# CopyCounter at /usr/include/boost/stacktrace/stacktrace.hpp:?
1# std::__1::__function::__value_func<void ()>::swap(std::__1::__function::__value_func<void ()>&) at /usr/lib/llvm-10/bin/../include/c++/v1/functional:?
2# ~__value_func at /usr/lib/llvm-10/bin/../include/c++/v1/functional:1825
3# __libc_start_main in /lib/x86_64-linux-gnu/libc.so.6
4# _start in ./bin/./src/csc_cpp/move_functors似乎在库中,函子和参数在swap中被复制,在invoke_的移动分配过程中。有两个问题:
发布于 2022-05-22 01:14:02
libstdc++和libc++采用不同的小对象优化策略.
在libc++中,如果存在以下情况,则可调用的存储在本地:
(注意: C++17中的分配器支持已从标准中删除。然而,实际上,在不破坏现有代码的情况下,它不能从标准库实现中删除,至少在很长一段时间内是如此。)
在libstdc++中,如果存在以下情况,则可调用的存储在本地:
(我正在讨论对齐问题,因为它在这里并不特别重要。)
libstdc++对何时使用小对象优化的选择意味着,无论何时应用,都可以通过复制为其提供存储空间的数组来复制可调用的对象。但是代码中的Functor和Argument类型并不是微不足道的可复制类型,因此lambda闭包类型也是不可复制的。lambda的存储是从lambda构建的--行外存储,而拥有可调用的存储是移动的。此后,它永远不需要被复制或移动。
另一方面,libc++将您的可调用存储在小对象缓冲区中。在分配给invoke_期间,它必须用默认构造的std::function交换包含小对象缓冲区中的闭包对象的std::function。这意味着可调用必须从一个std::function实例的小对象缓冲区移动到另一个实例的小对象缓冲区。libc++通过复制可调用的,然后销毁副本的源来完成此操作。
为什么libc++不使用移动构造函数?很明显是个窃听器。请参阅LLVM bug 33125,它是关于move构造函数的,而不是swap函数,但适用相同的原则。当源将可调用的可调用函数存储在小对象缓冲区中时,std::function的移动构造函数使用复制构造函数,而不是移动构造函数。原因是libc++使用类型擦除接口知道如何在忘记其类型的同时处理可调用,它是一个名为__base的类,它具有用于复制可调用的虚拟函数和用于销毁可调用的虚拟函数,但是没有任何可调用的函数--显然,添加移动支持会破坏ABI,因此在进一步的通知之前是无法完成的。
请注意,这只是一个错误,因为它的性能不佳。从某种意义上说,这不是一个不符合标准的错误。libstdc++和libc++都有有效的实现策略。标准没有说明允许复制可调用的次数。
最好的做法是更新您的代码,使其正确性不取决于可调用的复制次数。如果您真的无法承担副本的成本,但需要使用libc++进行构建,那么还有其他一些策略,如:
noexcept(false) (通过使用noexcept(false)复制构造函数捕获对象),使其不会进入小对象;或std::function中存储一个引用语义包装类,它拥有实际可调用的std::shared_ptr和转发到实际可调用对象的operator()。发布于 2022-05-21 20:13:42
部分答复:
如果使用初始化列表初始化invoke_:
class Invoker {
std::function<void()> invoke_;
int result_;
public:
template <class Functor, class... Args>
Invoker(Functor&& f, Args&&... args) :
invoke_([this, f = fwd(f), ...args = fwd(args)]() mutable {
result_ = f(fwd(args)...);
}) { }
};断言不败。所以,我猜这个交换是在默认初始化的invoke_和指定给invoke_的函子之间的。
https://stackoverflow.com/questions/72332743
复制相似问题