当前位置: 首页 > 知识库问答 >
问题:

lambda仿函数赋值变通方法

方绪
2023-03-14

下面的代码有问题吗?

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

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

编辑:为什么要初始化为<code>字符</code>数组?如果块足够大,可以从堆中分配大小为<code>sizeof(buffer_type)

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

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

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

共有3个答案

长孙阳成
2023-03-14

除了已经提到的对齐问题之外,您正在通过placement<code>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的代码中,< code>store是< code>static,这意味着一个lambda在另一个之上重复构造,就好像后者只是原始内存一样。

乐正涵忍
2023-03-14

我不明白。为什么使用<code>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)));
邵修诚
2023-03-14

您必须确保< code>store与< code>functor_type正确对齐。除此之外,我没有看到任何关于标准一致性的问题。但是,通过使数组成为非静态的,可以很容易地解决多线程问题,因为< code>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包装器。仅仅放置新函数是不够的,因为函子不会被正确销毁,它们拥有的资源(例如通过捕获的智能指针)也不会被释放。

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();
}

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

 类似资料:
  • 为什么它会打印?你能详细介绍一下所有的功能吗?

  • 通过函数构造函数创建变量时它们似乎被声明为VAL。由于VAL不能重新赋值(出于某些原因,我需要这样做),我想知道是否有办法在函数之前的一行代码中将变量声明为var,然后通过函数赋值。 请记住,我昨天才开始学习静态编程语言。我对所有替代方案都持开放态度。 公共趣味单打(enemyhealth:Int,enemyattack:Int,enemyname:String) 当我尝试重新分配敌人的健康时,我

  • 仿函数、仿函数类、函数等 无论喜欢或不喜欢,函数和类似函数的对象——仿函数——遍布STL。关联容器使用它们来使元素保持有序;find_if这样的算法使用它们来控制它们的行为;如果缺少它们,那么比如for_each和transform这样的组件就没有意义了;比如not1和bind2nd这样的适配器会积极地产生它们。 是的,在你看到的STL中的每个地方,你都可以看见仿函数和仿函数类。包括你的源代码中。

  • = 赋值操作符(它的左右两边不能有空白符) 不要搞混了 = 和 -eq,-eq 是比赋值操作更高级的测试。注意:等于号(=)根据环境的不同它可能是赋值操作符也可能是一个测试操作符。 例子 4-2. 简单的变量赋值 1 #!/bin/bash 2 # 裸变量 3 4 echo 5 6 # 什么时候变量是“裸”的?比如说,变量名前面没有$? 7 #当变量被赋值