C ++ 11 람다 구현 및 메모리 모델 클로저는 한 번

C ++ 11 클로저에 대해 올바르게 생각 std::function하는 방법과 구현 방법 및 메모리 처리 방법 에 대한 정보를 원합니다 .

나는 조기 최적화를 믿지 않지만 새로운 코드를 작성하는 동안 내 선택의 성능 영향을 신중하게 고려하는 습관이 있습니다. 또한 비 결정적 메모리 할당 / 할당 해제 일시 중지를 피해야하는 마이크로 컨트롤러 및 오디오 시스템에서 상당한 양의 실시간 프로그래밍을 수행합니다.

따라서 C ++ 람다를 사용하거나 사용하지 않을 때를 더 잘 이해하고 싶습니다.

내 현재 이해는 캡처 된 클로저가없는 람다가 C 콜백과 똑같다는 것입니다. 그러나 환경이 값 또는 참조로 캡처되면 스택에 익명 개체가 생성됩니다. 값 폐쇄가 함수에서 반환되어야 할 때 하나는 그것을 std::function. 이 경우 클로저 메모리는 어떻게됩니까? 스택에서 힙으로 복사됩니까? 가 해제 될 때마다 std::function해제 std::shared_ptr됩니까?

실시간 시스템에서 람다 함수 체인을 설정하여 B를 A에 연속 인수로 전달하여 처리 파이프 라인 A->B을 만들 수 있다고 상상합니다 . 이 경우 A 및 B 클로저는 한 번 할당됩니다. 스택 또는 힙에 할당되는지 확실하지 않지만. 그러나 일반적으로 실시간 시스템에서 사용하는 것이 안전 해 보입니다. 반면에 B가 반환하는 람다 함수 C를 생성하면 C에 대한 메모리가 반복적으로 할당되고 할당 해제되므로 실시간 사용에 적합하지 않습니다.

의사 코드에서 DSP 루프는 실시간으로 안전 할 것이라고 생각합니다. A가 해당 인수를 호출하는 블록 A와 B를 처리하고 싶습니다. 이 두 함수는 모두 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; }
}

후자의 경우 루프가 반복 될 때마다 클로저가 생성되지만 이전 예제와 달리 함수 호출과 같으므로 힙 할당이 이루어지지 않기 때문에 저렴합니다. 또한 컴파일러가 클로저를 “리프트”하고 인라인 최적화를 수행 할 수 있는지 궁금합니다.

이 올바른지? 감사합니다.



답변

내 현재 이해는 캡처 된 클로저가없는 람다가 C 콜백과 똑같다는 것입니다. 그러나 환경이 값 또는 참조로 캡처되면 스택에 익명 개체가 생성됩니다.

아니; 그것은 항상 스택에 생성 알 수없는 유형의 C ++ 객체. 캡쳐리스 람다 수 변환 (이것은 C가 호출 규칙에 적합한 지 구현 의존하지만) 함수 포인터에 있지만, 즉 의미하지 않는다 함수 포인터.

함수에서 값 클로저를 반환해야 할 때 std :: function으로 래핑합니다. 이 경우 클로저 메모리는 어떻게됩니까?

람다는 C ++ 11에서 특별한 것이 아닙니다. 다른 물체와 같은 물체입니다. 람다 식은 스택에서 변수를 초기화하는 데 사용할 수있는 임시를 생성합니다.

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의 생성자가 할당자를 취할 수있는 이유 입니다.

std :: function이 해제 될 때마다 해제됩니까? 즉, std :: shared_ptr처럼 참조 계산됩니까?

std::function내용 의 사본 을 저장합니다 . 거의 모든 표준 라이브러리 C ++ 유형과 마찬가지로 값 의미 체계를function 사용 합니다 . 따라서 복사 가능합니다. 복사 할 때 새 function개체는 완전히 분리됩니다. 또한 이동 가능하므로 추가 할당 및 복사없이 내부 할당을 적절하게 전송할 수 있습니다.

따라서 참조 카운팅이 필요하지 않습니다.

“메모리 할당”이 “실시간 코드에서 사용하기에 좋지 않음”과 같다고 가정하면 다른 모든 내용이 정확합니다.


답변

C ++ 람다 과부하로 단지 문법 설탕 주위 (익명) 펑터 클래스입니다 operator()std::functioncallables 주변 단지 래퍼 (즉 펑, 람다, C-기능, …) 않는 값으로 사본을 현재의에서 “고체 람다 객체를” 스택 범위- 힙까지 .

실제 생성자 / 재배치 수를 테스트하기 위해 테스트를 수행했습니다 (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

스택 할당 람다 객체에 대해 정확히 동일한 ctor / dtor 세트가 호출됩니다! (이제 스택 할당을 위해 Ctor를 호출하고 std :: function에서 생성하기 위해 Copy-ctor (+ 힙 할당)를 호출하고 shared_ptr 힙 할당 + 함수 생성을 위해 또 다른 하나를 호출합니다)


답변