문제의 람다는 실제로 상태 가 없습니다 .
검사 :
struct lambda {
auto operator()() const { return 17; }
};
그리고 우리가 가지고 있다면 lambda f;
그것은 빈 클래스입니다. 위의 내용은 lambda
람다와 기능적으로 유사 할 뿐만 아니라 (기본적으로) 람다가 구현되는 방식입니다! (또한 함수 포인터 연산자에 대한 암시 적 캐스트가 필요하며 이름 lambda
은 일부 컴파일러 생성 의사 GUI로 대체됩니다.)
C ++에서 개체는 포인터가 아닙니다. 그것들은 실제적인 것입니다. 데이터를 저장하는 데 필요한 공간 만 사용합니다. 개체에 대한 포인터는 개체보다 클 수 있습니다.
람다를 함수에 대한 포인터로 생각할 수 있지만 그렇지 않습니다. 를 auto f = [](){ return 17; };
다른 함수 나 람다에 다시 할당 할 수 없습니다 !
auto f = [](){ return 17; };
f = [](){ return -42; };
위의 내용은 불법 입니다. 호출 할 함수 f
를 저장할 공간이 없습니다. 해당 정보는 ! 값이 아닌 형식 으로 저장됩니다 .f
f
이 경우 :
int(*f)() = [](){ return 17; };
아니면 이거:
std::function<int()> f = [](){ return 17; };
더 이상 람다를 직접 저장하지 않습니다. 이 두 경우 모두 f = [](){ return -42; }
합법적입니다. 따라서이 경우 우리는 의 값에서 호출 하는 함수를 저장 f
합니다. 그리고 sizeof(f)
더 이상 1
은 아니지만 오히려 sizeof(int(*)())
더 큽니다 (기본적으로 예상대로 포인터 크기 이상이어야합니다. std::function
표준에서 암시하는 최소 크기 ( "내부"콜 러블을 특정 크기까지 저장할 수 있어야 함)). 실제로는 함수 포인터만큼 큽니다).
이 int(*f)()
경우 해당 람다를 호출 한 것처럼 동작하는 함수에 대한 함수 포인터를 저장합니다. 이것은 상태 비 저장 람다 (빈 []
캡처 목록 이있는 람다)에서만 작동합니다 .
에 std::function<int()> f
경우에는, 형태 소거 클래스 생성된다 std::function<int()>
(이 경우)의 내부 버퍼 사이즈 -1 람다의 복사본을 저장하는 새로운 (및 배치를 사용하는 예를보다 큰 람다보다 상태로 (전달되었는지 ), 힙 할당을 사용합니다).
추측으로, 이와 같은 것이 아마도 당신이 생각하는 일입니다. 람다는 서명으로 유형이 설명되는 객체입니다. C ++에서는 수동 함수 개체 구현 에 대해 람다를 비용없이 추상화 하기로 결정했습니다 . 이를 통해 람다를 std
알고리즘 (또는 유사)에 전달하고 알고리즘 템플릿을 인스턴스화 할 때 해당 내용을 컴파일러에 완전히 표시 할 수 있습니다. 람다에와 같은 유형이 있으면 std::function<void(int)>
해당 내용이 완전히 표시되지 않으며 손으로 만든 함수 객체가 더 빠를 수 있습니다.
C ++ 표준화의 목표는 수작업으로 만든 C 코드에 대한 오버 헤드가없는 고수준 프로그래밍입니다.
이제 f
실제로 무국적자 임을 이해 했으므로 머리 속에 또 다른 질문이 있어야합니다. 람다는 상태가 없습니다. 왜 크기가 0
없습니까?
짧은 대답이 있습니다.
C ++의 모든 객체는 표준에 따라 최소 크기가 1이어야하며 동일한 유형의 두 객체는 동일한 주소를 가질 수 없습니다. 유형의 배열은 T
요소가 sizeof(T)
분리 되어 있기 때문에 연결 됩니다 .
이제 상태가 없기 때문에 때때로 공간을 차지하지 않을 수 있습니다. 이것은 "혼자"일 때는 발생할 수 없지만 일부 상황에서는 발생할 수 있습니다. std::tuple
유사한 라이브러리 코드가이 사실을 악용합니다. 작동 방식은 다음과 같습니다.
람다는 operator()
오버로드 된 클래스와 동일하므로 상태 비 저장 람다 ( []
캡처 목록 포함)는 모두 빈 클래스입니다. 그들은이 sizeof
의 1
. 사실, 당신이 그들로부터 상속한다면 (허용됩니다!), 같은 유형의 주소 충돌을 일으키지 않는 한 그들은 공간을 차지 하지 않을 것 입니다. (이를 빈 기본 최적화라고합니다).
template<class T>
struct toy:T {
toy(toy const&)=default;
toy(toy &&)=default;
toy(T const&t):T(t) {}
toy(T &&t):T(std::move(t)) {}
int state = 0;
};
template<class Lambda>
toy<Lambda> make_toy( Lambda const& l ) { return {l}; }
는 sizeof(make_toy( []{std::cout << "hello world!\n"; } ))
것입니다 sizeof(int)
(: 당신이이 이름을 만들 필요가 아니라, 위가 아닌 평가 맥락에서 람다를 만들 수 없기 때문에 불법 auto toy = make_toy(blah);
을 수행 한 후 sizeof(blah)
, 그러나 그것은 단지 잡음). sizeof([]{std::cout << "hello world!\n"; })
여전히 1
(유사한 자격)입니다.
다른 장난감 유형을 만드는 경우 :
template<class T>
struct toy2:T {
toy2(toy2 const&)=default;
toy2(T const&t):T(t), t2(t) {}
T t2;
};
template<class Lambda>
toy2<Lambda> make_toy2( Lambda const& l ) { return {l}; }
이것은 람다의 두 복사본 을 가지고 있습니다. 그들은 같은 주소를 공유 할 수 없으므로, sizeof(toy2(some_lambda))
입니다 2
!
struct
와 함께operator()
)