【问题标题】:How to test for trivially copy assignable lambdas如何测试可简单复制的可分配 lambda
【发布时间】:2016-08-25 11:14:38
【问题描述】:

如果有人能给我一个关于如何测试函子的琐碎复制能力的提示,我将不胜感激(打算使用 lambda)。正如this question 中所解释的,lambda 是否可以简单地复制是由实现定义的。例如,对于本问题末尾显示的代码 gcc (5.4) 和 msvc (2015) 都触发断言,这些断言不是可简单复制分配的。

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

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

所以我有两个问题:

  1. 想象一下,在下面的test_functor() 中,我做new 放置,例如喜欢

    new (&buffer) F(functor)

    对于下面显示的那种 lambda,只使用 memcopy 这个缓冲区是否安全?我希望这应该是这样,因为对于所有情况,只有this 指针被捕获或被捕获的值是可简单复制的,但如果有人能证实这一点,那就太好了。

  2. 如何测试简单复制保存仿函数的内存是否等同于复制仿函数?如果第一个问题的答案是肯定的,那么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;
}

【问题讨论】:

  • 您应该使用is_trivially_copyable,而不是is_trivially_meow_constructibleis_trivially_meow_assignable。 Lambda 永远不会被简单地复制(或移动)赋值,因为它们的复制赋值运算符已被删除;这并不一定会阻止它们被简单地复制。
  • @T.C. - 你能在答案部分发表你的评论吗?我想将其标记为答案,因为您一针见血。我不知道 lambdas 的复制分配被删除了。确实is_trivially_copyable 正在通过。谢谢。

标签: c++ c++11 lambda


【解决方案1】:

不,这不安全。如果编译器说某些东西不能简单地复制,那就不能了。

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

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

修复非常简单。编写不需要简单可复制的 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 有足够的空间让 lambda 存储两个 std::strings。

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

您已经在“擦除”调用,这基本上需要存储一个函数指针,添加移动(可能还有复制)和销毁并没有那么多开销。

【讨论】:

  • 谢谢你的例子。我会看看它,但同时我会保留它们的东西(我正在测试std::function - 我认为使用 gcc 4.9 - 它正在为我正在使用的 lambdas 分配)。请参阅 T.C 的评论 - 这非常符合您的第一句话 :) - 我应该通过 std::is_trivially_copyable 测试微不足道的可复制性。最好的问候
  • @AndrzejO 大笑!我错过了。我认为编译器有点白痴。不过,如果它是个白痴,你不会说“我真的知道发生了什么”,这就是我的观点。 ;)
  • Yakk,我决定将您的答案标记为解决方案,因为 MSVC 似乎仍在声称此类 lambda 表达式不可轻易复制。我打赌编译器会出错,但在这种情况下,我别无选择,只能自己复制。
猜你喜欢
  • 1970-01-01
  • 2019-07-04
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-09-02
  • 2019-05-06
  • 1970-01-01
相关资源
最近更新 更多