lambda函数赋值解决方法

lambda functors assignment workaround

本文关键字:方法 解决 赋值 函数 lambda      更新时间:2023-10-16

下面的代码有问题吗?

#include <iostream>
#include <type_traits>
template <typename T>
void assign_lambda(T&& f)
{
  typedef typename std::remove_reference<T>::type functor_type;
  typedef typename std::aligned_storage<sizeof(functor_type),
    std::alignment_of<functor_type>::value>::type buffer_type;
  static char store[sizeof(buffer_type)];
  auto const p(new (store) functor_type(std::forward<T>(f)));
  (*p)();
}
int main()
{
  for (int i(0); i != 5; ++i)
  {
    assign_lambda([i](){ std::cout << i << std::endl; });
  }
  return 0;
}

不过,我担心这样做可能是不标准的和/或危险的。

编辑:为什么要初始化为char数组?可以从堆中分配一个大小为sizeof(buffer_type)的块,并重复分配(即避免重复的内存分配),如果该块足够大的话。

void*运算符new(std::size_t size);

效果:由新表达式(5.3.4)调用的分配函数(3.7.4.1),用于分配适当对齐的存储大小字节,以表示该大小的任何对象。

我想如果我从堆中分配,那么对齐问题就会消失。

您必须确保storefunctor_type正确对齐。除此之外,我没有看到任何关于标准一致性的问题。但是,您可以通过使数组非静态来轻松解决多线程问题,因为sizeof提供了compiletime常量。

§5.3.4,14要求对齐:

[注意:当分配函数返回null以外的值时,它必须是一个指向已为对象保留空间的存储块的指针。存储块被认为是适当对齐的,并且具有请求的大小。[…]-结束注释]

还有另一段关于对齐的§3.7.4.1,但该段明确不适用于新的放置(§18.6.1.3,1)

要正确对齐,您可以执行以下操作:

template <typename T>
void assign_lambda(T&& f)
{
  typedef typename std::remove_reference<T>::type functor_type;
  //alignas(functor_type) char store[sizeof(functor_type)];
  std::aligned_storage<sizeof(functor_type), 
            std::alignment_of<functor_type>::value>::type store;
  auto const p(new (&store) functor_type(std::forward<T>(f)));
  (*p)();
  //"placement delete"
  p->~functor_type();
}

更新:上面显示的方法与只使用一个正常变量没有什么不同:

template <typename T>
void assign_lambda(T&& f)
{
  typedef typename std::remove_reference<T>::type functor_type;
  functor_type func{std::forward<T>(f)};
  func();
}

如果使成为函数内的静态变量,则需要为不可赋值的函数提供RAII包装。仅仅放置newing是不够的,因为函数不会被正确销毁,它们所拥有的资源(例如通过捕获的智能指针)也不会被释放。

template <typename F>
struct RAIIFunctor {
  typedef typename std::remove_reference<F>::type functor_type;
  std::aligned_storage<sizeof(functor_type), 
            std::alignment_of<functor_type>::value>::type store;
  functor_type* f;
  RAIIFunctor() : f{nullptr} {}
  ~RAIIFunctor() { destroy(); }
  template <class T>
  void assign(T&& t) {
    destroy();
    f = new(&store) functor_type {std::forward<T>(t)};
  }
  void destroy() {
    if (f) 
      f->~functor_type();
    f = nullptr;
  }
  void operator() {
    (*f)();
  }
};

template <typename T>
void assign_lambda(T&& f)
{
  static RAIIFunctor<T> func;
  func.assign(std::forward<T>(f));
  func();
}

你可以在这里看到代码的作用

我不明白。为什么使用aligned_storage只是为了获得一些大小来创建未初始化的存储,而不是。。。使用它提供的对齐存储?这几乎就像从柏林到里斯本,先乘坐柏林->里斯本的航班,然后乘坐里斯本->莫斯科的航班。

  typedef typename std::remove_reference<T>::type functor_type;
  typedef typename std::aligned_storage<sizeof(functor_type),
    std::alignment_of<functor_type>::value>::type buffer_type;
  static buffer_type store;
  auto const p(new (&store) functor_type(std::forward<T>(f)));

除了前面提到的对齐问题外,您正在通过放置new创建lambda的副本,但没有销毁该副本。

以下代码说明了问题:

// This class plays the role of the OP's lambdas
struct Probe {
    Probe() { std::cout << "Ctr" << 'n'; }
    Probe(const Probe&) { std::cout << "Cpy-ctr" << 'n'; }
    ~Probe() { std::cout << "Dtr" << 'n'; }
};
// This plays the role of the OP's assign_lambda
void f(const Probe& p) {
    typedef typename std::aligned_storage<sizeof(Probe),
        std::alignment_of<Probe>::value>::type buffer_type;
    static buffer_type store;
    new (&store) Probe(p);
}
int main() {
    Probe p;
    // This plays the role of the loop
    f(p);
    f(p);
    f(p);
}

输出为:

Ctr
Cpy-ctr
Cpy-ctr
Cpy-ctr
Dtr

因此,建造了4个物体,只有一个被摧毁。

此外,在OP的代码中,storestatic,这意味着一个lambda被重复构建在另一个之上,就好像后者只是原始内存一样。