2

下面的代码有问题吗?

#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*operator new(std::size_t size);

效果:new-expression (5.3.4) 调用的分配函数 (3.7.4.1) 分配 size 字节的存储,适当对齐以表示该大小的任何对象。

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

4

3 回答 3

5

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

§5.3.4,14要求对齐:

[ 注意:当分配函数返回一个非空值时,它必须是一个指向已为对象保留空间的存储块的指针。假定存储块已适当对齐并具有请求的大小。[...] - 尾注]

还有另一段,第 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 包装器用于不可分配的函子。仅仅placement-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();
}

您可以在此处查看实际代码

于 2013-07-11T10:18:12.260 回答
3

我不明白。为什么人们会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)));
于 2013-07-11T10:37:50.773 回答
2

除了已经提到的对齐问题之外,您正在通过放置创建 lambda 的副本,new但您不会破坏副本。

下面的代码说明了这个问题:

// 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 在另一个之上重复构建,就好像后者只是原始内存一样。

于 2013-07-11T10:57:31.413 回答