나는 C ++에서 일부 람다의 메모리로 작업하고 있지만 크기에 약간 의아해합니다.
내 테스트 코드는 다음과 같습니다.
#include <iostream>
#include <string>
int main()
{
auto f = [](){ return 17; };
std::cout << f() << std::endl;
std::cout << &f << std::endl;
std::cout << sizeof(f) << std::endl;
}
여기에서 실행할 수 있습니다 : http://fiddle.jyt.io/github/b13f682d1237eb69ebdc60728bb52598
출력은 다음과 같습니다.
17
0x7d90ba8f626f
1
이것은 내 람다의 크기가 1임을 나타냅니다.
-
이것이 어떻게 가능한지?
-
람다는 최소한 그 구현에 대한 포인터 여야하지 않습니까?
답변
문제의 람다는 실제로 상태 가 없습니다 .
검사 :
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
!
답변
람다는 함수 포인터가 아닙니다.
람다는 클래스의 인스턴스입니다. 코드는 다음과 거의 동일합니다.
class f_lambda {
public:
auto operator() { return 17; }
};
f_lambda f;
std::cout << f() << std::endl;
std::cout << &f << std::endl;
std::cout << sizeof(f) << std::endl;
람다를 나타내는 내부 클래스에는 클래스 멤버가 없으므로 sizeof()
1입니다 ( 다른 곳에 적절하게 언급 된 이유로 0 일 수 없음 ).
람다가 일부 변수를 캡처하는 경우 클래스 멤버와 동일하며 sizeof()
그에 따라 표시됩니다.
답변
컴파일러는 람다를 다음 구조체 유형으로 거의 변환합니다.
struct _SomeInternalName {
int operator()() { return 17; }
};
int main()
{
_SomeInternalName f;
std::cout << f() << std::endl;
}
이 구조체에는 비 정적 멤버가 없으므로 빈 구조체 인 1
.
비어 있지 않은 캡처 목록을 람다에 추가하자마자 변경됩니다.
int i = 42;
auto f = [i]() { return i; };
번역 할
struct _SomeInternalName {
int i;
_SomeInternalName(int outer_i) : i(outer_i) {}
int operator()() { return i; }
};
int main()
{
int i = 42;
_SomeInternalName f(i);
std::cout << f() << std::endl;
}
생성 된 구조체는 이제 int
캡처를 위해 비 정적 멤버 를 저장해야 하므로 크기가 sizeof(int)
. 더 많은 것을 캡처할수록 크기가 계속 커집니다.
(구조체 비유를 소금과 함께 생각해보세요. 람다가 내부적으로 작동하는 방식을 추론하는 좋은 방법이지만 컴파일러가 수행 할 작업을 문자 그대로 번역 한 것은 아닙니다)
답변
람다는 mimumum에서 구현에 대한 포인터가되어야하지 않습니까?
반드시 그런 것은 아닙니다. 표준에 따르면 이름이 지정되지 않은 고유 한 클래스의 크기는 구현에 따라 정의됩니다 . [expr.prim.lambda] , C ++ 14 (강조 내) 에서 발췌 :
람다 표현식의 유형 (클로저 객체의 유형이기도 함)은 아래에 설명 된 속성을 가진 고유 한 명명되지 않은 비 유니온 클래스 유형 (클로저 유형이라고 함)입니다.
[…]
구현은 아래 에 설명 된 것과 다르게 클로저 유형을 정의 할 수 있습니다. 단, 다음을 변경하는 것 외에는 프로그램의 관찰 가능한 동작을 변경하지 않습니다 .
– 폐쇄 형의 크기 및 / 또는 정렬 ,
— 클로저 유형이 간단하게 복사 가능한지 여부 (9 절),
— 클로저 유형이 표준 레이아웃 클래스인지 여부 (Clause 9) 또는
— 클로저 유형이 POD 클래스인지 여부 (Clause 9)
귀하의 경우-사용하는 컴파일러의 경우-크기가 1로 표시되지만 고정 된 것은 아닙니다. 컴파일러 구현에 따라 다를 수 있습니다.
답변
에서 http://en.cppreference.com/w/cpp/language/lambda :
람다 식은 다음 을 포함하는 가장 작은 블록 범위, 클래스 범위 또는 네임 스페이스 범위에서 선언되는 클로저 유형으로 알려진 고유 한 명명되지 않은 비 결합 비 집계 클래스 유형의 명명되지 않은 prvalue 임시 개체를 구성합니다. 람다 식.
lambda-expression이 사본으로 무엇이든 캡처하는 경우 (암시 적으로 캡처 절 [=]을 사용하거나 문자 &를 포함하지 않는 캡처 (예 : [a, b, c])를 사용 하여 명시 적으로), 클로저 유형에는 이름이 지정되지 않은 비 정적 데이터가 포함됩니다. 지정되지 않은 순서로 선언 된 members 는 캡처 된 모든 엔티티의 사본을 보유합니다.
참조 로 캡처 된 엔티티의 경우 (기본 캡처 [&] 사용 또는 & 문자를 사용할 때 (예 : [& a, & b, & c])) 클로저 유형에서 추가 데이터 멤버가 선언되면 지정되지 않습니다.
에서 http://en.cppreference.com/w/cpp/language/sizeof
빈 클래스 유형에 적용하면 항상 1을 반환합니다.