람다의 크기가 1 바이트 인 이유는 무엇입니까? 작업하고 있지만 크기에 약간

나는 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를 저장할 공간이 없습니다. 해당 정보는 ! 값이 아닌 형식 으로 저장됩니다 .ff

이 경우 :

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()오버로드 된 클래스와 동일하므로 상태 비 저장 람다 ( []캡처 목록 포함)는 모두 빈 클래스입니다. 그들은이 sizeof1. 사실, 당신이 그들로부터 상속한다면 (허용됩니다!), 같은 유형의 주소 충돌을 일으키지 않는 한 그들은 공간을 차지 하지 않을 것 입니다. (이를 빈 기본 최적화라고합니다).

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을 반환합니다.


답변