如何测试平易复制可分配的lambdas

时间:2016-08-25 11:14:39

标签: c++ c++11 lambda

如果有人能给我一个关于如何测试仿函数的简单复制能力的提示(我会使用lambdas),我会感激不尽。正如this question中所解释的那样,实现定义了lambda是否可以轻易复制。例如,对于此问题末尾显示的代码,gcc(5.4)和msvc(2015)都会触发断言,这些断言不是可以轻易复制的。

我希望这些类型的lambdas由struct表示,保持this指针并拥有每个捕获值的副本(如果有的话)。因此,它们似乎都是可以轻易复制的 - 至少在捕获的值可以轻易复制的情况下。

我推动这个问题的真实用例是我使用了一个回调(std::function的简约版本,它没有分配并且用于非常简单的仿函数),它保留了一个固定的缓冲区,在其中复制构造(就地)通过了仿函数。然后我希望能够复制/分配这些回调,但为了使这个工作(开箱即用),这些固定缓冲区的简单mem复制应该等同于保存在其中的仿函数的应对/分配。

所以我有两个问题:

  1. 想象一下,在test_functor()下面,new展示位置{}像

    new (&buffer) F(functor)

    这个缓冲区只有memcopy对于下面显示的lambda类型是否安全?我希望这应该是这样的,因为对于所有情况,只捕获this指针或捕获的值是可以轻易复制的,但是如果有人能够证实这一点会很好。

  2. 如何测试保存仿函数的内存的简单复制是否等同于仿函数的复制?如果第一个问题的答案是肯定的,那么std::is_trivially_copy_assignable就不是正确的答案。

  3. #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;
    }
    

1 个答案:

答案 0 :(得分:6)

不,这不安全。如果编译器说某些东西不能轻易复制,那就不可能。

可能会有效。但它起作用并不意味着安全

即使今天有效,但明天它会在编译器更新后停止工作。

修复非常简单。写一个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
};

live example

现在这里是一个妙语。 std::function几乎肯定已经为您做了

放置一个带有无投掷移动的小型类型并在std::function中构造并询问创建是否可以抛出。我猜你的实现将使用SBO将类型存储在那里。

MSVC 2015我认为有足够的空间容纳一个存储两个std::string的lambda。

正确做事的开销是适度的(两个指针,一个小的间接)。您可以将存储成本降低到每个实例一个指针,代价是更多间接(将表格粘贴到&#34;手动vtable&#34;在工厂函数中存储为静态本地:我可以提供示例的链接如果它没有点亮一个灯泡),但有2个擦除的方法也可以将它们存储在本地(3+以上考虑静态表),除非空间非常宝贵。

你已经&#34;擦除&#34;调用,基本上需要存储一个函数指针,添加移动(也许复制)和销毁不是更多的开销。