C ++ 11拉姆达执行和存储器模型(C++11 lambda implementation and

2019-06-17 22:54发布

我想就如何正确思考C ++ 11的关闭和一些信息std::function在它们的实现方式和内存是如何处理的条款。

虽然我不过早优化相信,我有仔细考虑我的选择对性能的影响,同时编写新代码的习惯。 我也做了相当数量的实时节目,例如在微控制器和音频系统,其中,非确定性的内存分配/释放暂停是要避免的。

因此,我想更好地了解何时使用或不使用C ++ lambda表达式。

我现在的理解是,没有捕获关闭了lambda酷似一个C回调。 然而,当环境或者通过值或引用捕获,在堆栈上产生一个匿名对象。 当值闭合必须从函数返回,一个把它包装std::function 。 恰巧在这种情况下,封闭的内存呢? 它与堆栈堆复制? 难道是释放的时候std::function被释放,即是引用计数像std::shared_ptr

我想,在实时系统中,我可以设置lambda函数链,传递B,为延续参数为A,以使得处理流水线A->B被创建。 在这种情况下,A和B关闭将分配一次。 虽然我不知道这是否会在堆栈或堆进行分配。 但是一般这似乎是安全的实时系统中使用。 上如果乙构建一些lambda函数C,将其返回另一方面,那么对于C中的存储器将被分配和解除分配反复,这将不会是实时使用是可接受的。

在伪代码,一个DSP循环,我认为这将是实时的安全。 我想执行处理块A,然后B,其中A调用它的参数。 这两个函数返回std::function对象,所以f将是一个std::function对象,在其环境保存在堆上:

auto f = A(B);  // A returns a function which calls B
                // Memory for the function returned by A is on the heap?
                // Note that A and B may maintain a state
                // via mutable value-closure!
for (t=0; t<1000; t++) {
    y = f(t)
}

和一个我认为可能是坏的实时代码使用方法:

for (t=0; t<1000; t++) {
    y = A(B)(t);
}

而一个地方,我认为堆栈存储器很可能用于关闭:

freq = 220;
A = 2;
for (t=0; t<1000; t++) {
    y = [=](int t){ return sin(t*freq)*A; }
}

在后一种情况下,封闭件在循环的每一次迭代构造,但在前面的例子不同于它便宜,因为它就像一个函数调用,没有堆分配制成。 此外,我想知道如果编译器可以“提升”关闭并进行内联的优化。

它是否正确? 谢谢。

Answer 1:

我现在的理解是,没有捕获关闭了lambda酷似一个C回调。 然而,当环境或者通过值或引用捕获,在堆栈上产生一个匿名对象。

没有; 它始终是一个C ++具有未知类型的对象,在堆栈上产生。 甲捕获更少的λ可被转换成一个函数指针(虽然它是否适合对C调用约定是依赖于实现的),但是,这并不意味着它一个函数指针。

当值闭合必须从函数返回,一个把它包装的std ::功能。 恰巧在这种情况下,封闭的内存呢?

一个lambda是不是在C ++ 11什么特别的东西。 这就像任何其他对象的对象。 lambda表达式导致暂时的,这可以被用来初始化堆栈上的变量:

auto lamb = []() {return 5;};

lamb是一个堆栈的对象。 它有一个构造函数和析构函数。 ,它会跟所有的C ++规则这一点。 的类型的lamb将包含被捕获的值/参考文献; 它们将是对象的成员,就像任何其它类型的任何其他对象的成员。

你可以给它一个std::function

auto func_lamb = std::function<int()>(lamb);

在这种情况下,它会得到的值的副本 lamb 。 如果lamb夺取了由任何有价值的东西,就不会有这些值的两个副本; 一个在lamb ,和一个在func_lamb

当电流范围结束, func_lamb就会被破坏,随后lamb ,按照清理堆栈变量的规则。

你可以很轻松地分配一个在堆上:

auto func_lamb_ptr = new std::function<int()>(lamb);

的确切位置对于一个内容的存储器std::function变为是依赖于实现的,但所采用的类型擦除std::function通常需要至少一个存储器分配。 这就是为什么std::function的构造函数可以接受一个allocator。

难道是释放的时候的std ::功能中解脱出来,即是引用计数就像一个std :: shared_ptr的?

std::function保存其内容的副本 。 像几乎所有的标准库的C ++类, function使用值语义 。 因此,能够复制; 当它被复制,新的function目的是完全分开的。 这也是可移动的,所以任何内部分配能够适当,而无需更多分配和复制传送。

因此,有没有必要引用计数。

否则,你的状态一切都是正确的,假定“内存分配”等同于“坏到实时代码中使用”。



Answer 2:

C ++中的λ只是一个语法糖各地(匿名)函数子类具有重载operator()std::function仅仅是围绕可调用的包装(即仿函数,lambda表达式,C-功能,...),这确实按值复制 “固体拉姆达对象”从当前堆栈范围-到

为了测试实际的构造函数的数量/ relocatons我做了一个试验(用包裹到shared_ptr但它不是这样的另一个层面)。 你自己看:

#include <memory>
#include <string>
#include <iostream>

class Functor {
    std::string greeting;
public:

    Functor(const Functor &rhs) {
        this->greeting = rhs.greeting;
        std::cout << "Copy-Ctor \n";
    }
    Functor(std::string _greeting="Hello!"): greeting { _greeting } {
        std::cout << "Ctor \n";
    }

    Functor & operator=(const Functor & rhs) {
        greeting = rhs.greeting;
        std::cout << "Copy-assigned\n";
        return *this;
    }

    virtual ~Functor() {
        std::cout << "Dtor\n";
    }

    void operator()()
    {
        std::cout << "hey" << "\n";
    }
};

auto getFpp() {
    std::shared_ptr<std::function<void()>> fp = std::make_shared<std::function<void()>>(Functor{}
    );
    (*fp)();
    return fp;
}

int main() {
    auto f = getFpp();
    (*f)();
}

它使这样的输出:

Ctor 
Copy-Ctor 
Copy-Ctor 
Dtor
Dtor
hey
hey
Dtor

完全相同的一组构建函数/ dtors将被要求在栈上分配的拉姆达对象! (现在它调用CTOR为堆栈分配,复制构造函数(+堆ALLOC)中,构建它的std ::功能,另一种用于制造shared_ptr的堆分配+的功能结构)



文章来源: C++11 lambda implementation and memory model