如何测试普通复制可赋值的lambdas
How to test for trivially copy assignable lambdas
如果有人能给我一个关于如何测试一个函子的微不足道的可复制性的提示,我将不胜感激(lambdas意味着要使用)。正如在这个问题中所解释的,lambda是否可复制是由实现定义的。例如,对于这个问题末尾显示的代码,gcc(5.4)和msvc(2015)都发出断言,这些不是简单的复制可分配的。
我期望这些类型的lambda由struct
表示,保持this
指针,并具有每个捕获值的副本(如果有的话)。因此,它们看起来都是非常可复制的——至少对于捕获值非常可复制的情况来说是这样。
我真正的用例驱动这个问题是,我正在使用一个回调(std::function
的极简版本,不分配,意味着非常简单的函子),保持一个固定的缓冲区,它复制构造(在适当的地方)传递函子。然后我期望能够复制/赋值这些回调函数,但为了使其工作(开箱即用),这些固定缓冲区的简单内存复制应该等同于复制/赋值保存在其中的函子。
我有两个问题:
-
想象在
test_functor()
下面我做new
的位置,例如new (&buffer) F(functor)
对于下面所示的那种lambdas,仅仅
memcopy
这个缓冲区是安全的吗?我希望这应该是这样的,因为在所有情况下,只有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;
}
不,不安全。如果编译器说某些东西不能简单地复制,它就不能被复制。
它可能工作。但它工作并不意味着它是安全的。
即使它今天工作,但明天它停止工作后,编译器更新。
修复非常简单。写一个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
};
生活例子。
现在是笑点。std::function
几乎可以肯定已经为您做了。
在std::function
中放置一个具有无抛出移动和构造的小类型,并询问创建是否可以抛出。我猜你的实现将使用SBO来存储类型。
MSVC 2015我认为有足够的空间为lambda存储两个std::string
s。
做正确事情的开销是适度的(两个指针和一点间接)。你可以将存储成本降低到每个实例一个指针,但代价是更间接(将表粘贴到一个"手动虚函数表"中,作为静态本地存储在工厂函数中:如果这还不亮起的话,我可以提供一个示例链接),但是如果有2个擦除方法,则不妨将它们存储在本地(考虑静态表的3+),除非空间有限。
您已经"擦除"调用,这基本上需要存储一个函数指针,添加移动(可能是复制)和销毁并没有那么多的开销。
- CRTP 中的复制赋值运算符 - gcc vs clang 和 msvc
- 如何在双向链表上实现复制赋值?
- 复制赋值函数如何访问另一个对象的私有成员(Stroustroup 原则和实践书)?
- 为什么基类中的复制和交换会导致派生类中的复制赋值运算符被隐式删除?
- 在c++中重载复制赋值运算符
- 复制构造函数和复制赋值运算符是否应具有相同的语句?
- 移动赋值运算符与复制赋值运算符
- Gcc 使用 memcpy 作为隐式复制赋值运算符,而不是成员复制
- 为什么 GCC 拒绝复制赋值操作中的常量引用
- 复制赋值构造函数中的aligned_alloc内存块在释放时崩溃
- 复制赋值和复制构造函数(代码C++的差异)
- 如何在没有复制赋值运算符的情况下交换两个对象
- 对于具有抛出复制构造函数和noexcept-by-value复制赋值的类,is_nothrow_copy_assigna
- 模拟 C++ 中 lambda 的复制赋值运算符
- 重载复制赋值运算符
- 为什么C++编译器会创建复制构造函数和复制赋值运算符
- 为什么在进行复制赋值之前调用复制构造函数
- 为什么当复制赋值函数不返回任何内容时编译器不引发错误?
- 默认的复制构造函数和复制赋值运算符给出奇怪的错误
- 重载运算符 = 返回 void 是否不可能成为复制赋值运算符