我很感激,如果有人能给我一个关于如何测试仿函数的简单复制能力的暗示(lambda用于表示).正如在这个问题中所解释的那样,实现定义了lambda是否可以轻易复制.例如,对于本问题末尾显示的代码,gcc(5.4)和msvc(2015)都会触发断言,这些断言不是简单的可复制的.
我希望通过struct保持this指针和每个捕获值(如果有的话)的副本来表示这些类型的lambda .所以它们看起来都是可以轻易复制的 - 至少对于捕获的值是可以轻易复制的情况.
我驱动这个问题的真实用例是我正在使用一个回调(简化版本std::function不分配并且用于非常简单的仿函数),它保留了一个固定的缓冲区,在其中复制构造(就地)传递了仿函数.然后我希望能够复制/分配这些回调,但为了使其工作(开箱即用),这些固定缓冲区的简单mem复制应该等同于保存在其中的仿函数的复制/分配.
所以我有两个问题:
想象一下,在test_functor()下面我做new位置,例如
new (&buffer) F(functor)
memcopy这个缓冲区对于下面显示的lambda 是否安全?我希望这应该是这样的,因为对于所有情况,只this捕获指针或捕获的值是可以轻易复制的,但如果有人可以证实这一点会很好.
如何测试保存仿函数的内存的简单复制是否等同于仿函数的复制?如果第一个问题的答案是肯定的,那么就std::is_trivially_copy_assignable不是正确的答案.
#include <type_traits>
template <typename F>
void test_functor(const F& functor)
{
static_assert(std::is_trivially_destructible<F>::value,
"Functor not trivially destructible");
static_assert(std::is_trivially_copy_constructible<F>::value,
"Functor not trivially copy constructible");
static_assert(std::is_trivially_copy_assignable<F>::value,
"Functor not trivially copy assignable");
}
struct A
{
void test() { test_functor([this]() { }); }
};
struct B
{
void test() { test_functor([this](int v) { value = v; }); }
int value;
};
struct C
{
void test(int v) { test_functor([=]() { value = v; }); }
int value;
};
int main()
{
A a;
B b;
C c;
a.test();
b.test();
c.test(1);
return 0;
}
Run Code Online (Sandbox Code Playgroud)
不,这不安全.如果编译器说某些东西不能轻易复制,那就不可能.
它可能会奏效.但它的工作并不意味着它是安全的.
即使它今天有效,但明天它会在编译器更新后停止工作.
修复非常简单.写一个SBO类型(小缓冲区优化),不需要简单的可复制.
template<std::size_t S, std::size_t A>
struct SBO {
void(*destroy)(SBO*) = nullptr;
// void(*copy_ctor)(SBO const* src, SBO* dest) = nullptr;
void(*move_ctor)(SBO* src, SBO* dest) = nullptr;
std::aligned_storage_t< S, A > buffer;
void clear() {
auto d = destroy;
destroy = nullptr;
// copy_ctor = nullptr;
move_ctor = nullptr;
if (d) d(this);
}
template<class T, class...Args>
T* emplace( Args&&... args ) {
static_assert( sizeof(T) <= S && alignof(T) <= A, "not enough space or alignment" );
T* r = new( (void*)&buffer ) T(std::forward<Args>(args)...);
destroy = [](SBO* buffer) {
((T*)&buffer->buffer)->~T();
};
// do you need a copy ctor? If not, don't include this:
//copy_ctor = [](SBO const* src, SBO* dest) {
// auto s = (T const*)&src.buffer;
// dest->clear();
// dest->emplace<T>( *s );
//};
move_ctor = [](SBO* src, SBO* dest) {
auto* s = (T*)&src->buffer;
dest->clear();
dest->emplace<T>( std::move(*s) );
src->clear();
};
return r;
}
SBO() = default;
SBO(SBO&& o) {
if (o.move_ctor) {
o.move_ctor(&o, this);
}
}
SBO& operator=(SBO&& o) {
if (this == &o) return *this; // self assign clear, which seems surprising
if (o.move_ctor) {
o.move_ctor(&o, this);
}
return *this;
}
// do you need a copy ctor? If so, implement `SBO const&` ctor/assign
};
Run Code Online (Sandbox Code Playgroud)
实例.
现在这里是一个妙语. std::function几乎可以肯定已经为你做了这件事.
把一个小型的无投掷移动并构造成a std::function并询问创造是否可以投掷.我猜你的实现将使用SBO将类型存储在那里.
MSVC 2015我认为有足够的空间存放两个std::strings 的lambda .
做正确事情的开销是适度的(两个指针,一个小的间接).您可以将存储成本降低到每个实例一个指针,代价是更多间接(将表格粘贴到工厂函数中作为静态本地存储的"手动表格"中:如果不是,我可以提供示例的链接点亮一个灯泡),但有2个擦除的方法也可以在本地存储它们(在3+以上考虑静态表),除非空间非常宝贵.
你已经在"擦除"调用,这基本上需要存储一个函数指针,添加移动(也许是复制)和销毁不是那么多开销.