2013-07-11 3 views
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);

효과 : 할당 함수 (3.7.4.1)는 해당 크기의 모든 객체를 나타 내기 위해 적절히 정렬 된 저장소의 크기 바이트를 할당하기 위해 새 표현식 (5.3.4)을 호출합니다.

힙에서 할당하면 정렬 문제가 사라집니다.

+0

다중 스레드 환경에서 제대로 작동하지 않습니다 – bobah

+0

@bobah mutex가 수정할 수없는 것은 없습니다. – user1095108

+3

'std :: function'의 문제점은 무엇입니까? – sehe

답변

5

storefunctor_type의 적절한 정렬이 있는지 확인해야합니다. 그 외에도 표준 준수와 관련하여 어떤 문제도 보이지 않습니다. 그러나 sizeof은 컴파일 타임 상수를 제공하므로 배열을 nonstatic으로 만들어 멀티 스레딩 문제를 쉽게 해결할 수 있습니다.

배향 §5.3.4,14이 요구된다

[참고 : 할당 함수가 널이 아닌 값을 반환 할 때, 스토리지의 블럭에 대한 포인터되어야하는 개체의 공간이 예약되었습니다. 저장소 블록은 적절하게 정렬되고 요청 된 크기로 간주됩니다. [...] - end note]

정렬에 관한 §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(); 
} 

당신은 내가 그것을 얻을하지 않는 행동 here

+0

올바른 정렬이되지 않으면 어떻게됩니까? 표준에서 따옴표를 주시겠습니까? – user1095108

+0

적절한 정렬을 제공하지 않으면 동작이 정의되지 않습니다. 실제로 x86에서 "조금 느린 경우"는 "다른 작업에서는"버스 오류 "로 분류됩니다. –

+0

또한'aligned_storage' 트릭도 괜찮습니까? – user1095108

3

의 코드를 볼 수 있습니다. 왜 aligned_storage을 사용하여 초기화되지 않은 저장소를 만드는 대신 크기가 좀 나가면 정렬 된 저장소를 사용하는 것일까 요? 그것은 거의 베를린에서 리스본으로 여행하는 것과 같습니다 -> 리스본 비행에 이어 리스본 -> 모스크바 비행.이미 배치 new을 통해 람다의 복사본을 만들하지만 당신은 사본을 파괴하지 않는 언급 정렬 문제 외에도

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))); 
+0

나를 'aligned_storage'라고 가르쳐 주셔서 감사합니다 - 지금까지'alignof'와'alignas'에 대해서만 알고있었습니다. –

+0

배치 'new'에 대한 내 이해는'new'가 주어진 블록의 시작 부분에 대한 포인터를 반환하지 않을 수도 있지만, 정렬 요구 사항을 만족시키기 위해 약간 앞으로 나아갈 수 있습니다. 따라서 때때로 크기보다 많은 저장 공간이 필요합니다. 할당 한 객체 더 나아가 내 주요 관심사는 내 코드가 표준을 준수하는지 확인하는 것이 었습니다. 너도 잘 될거야, 이해 하겠지만, 내 코드도 괜찮아. – user1095108

+0

아니요, placement new는 포인터에 아무런 영향을 미치지 않으므로 코드가 정렬 요구 사항이 충족되었음을 보장하지 못했습니다. –

2

.

// 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이며 이것은 하나의 람다가 마치 원시 메모리 인 것처럼 반복적으로 다른 하나의 람다가 반복적으로 구성된다는 것을 의미합니다.

+0

'static' 객체는 응용 프로그램의 수명을 가지므로 정적 람다의 소멸자는 한 번만 호출됩니다. 나는 여기서 람다를 정적 "람다 (lambda)"에 복사하려고 시도하고있다. – user1095108

+1

@ user1095108 : 람다 'a'에 람다 'b'를 할당하지 않았습니다. 'a'가 차지하는 메모리를 마치 원시 메모리처럼 사용하고'b' 복사본을 만듭니다. 객체'a'는 적절히 소멸되지 않습니다. 기본적으로 동일한 메모리 주소에 두 개의 서로 다른 객체를 만들려고 시도하고 있습니다 (다른 하나의 하위 객체는 아닙니다). 허용되지 않습니다. –

+0

각 과제 전에 전화를 걸기 위해 커스텀 Deleter로이 문제를 해결할 수 없습니까? – user1095108