11. std::function 의 타입 소거와 람다 캡처 저장(SBO·값 의미론)

난이도 상 해설 보기 →

결함을 모두 찾고 원인·수정안·더 나은 설계를 제시하라. 마커 (A)(B) 는 주목 위치 힌트다.

결함 코드 · C++
// 주제: std::function 의 타입 소거(type erasure)와 람다 캡처 저장
//       — 소형 버퍼 최적화(SBO)에 따른 힙 할당, 그리고 값(복사) 의미론.
//
// 과제: 아래 프로그램의 출력을 예측하고 다음을 내부 동작으로 설명하라.
//   1) 무캡처 람다 f1, 작은 캡처 f2(int), 큰 캡처 f3(64바이트)를 std::function 에
//      넣을 때 각각 힙 할당이 몇 번 일어나는가? 그 차이는 무엇 때문인가?
//   2) std::function 을 복사(f4 = f3)하면 무슨 일이 일어나는가? 캡처한 상태는
//      공유되는가, 복제되는가? (Tracer 복사 횟수로 관찰)
//   3) std::function 이 "임의의 호출가능 객체를 같은 타입으로 담는" 것을 어떻게
//      가능하게 하는가(타입 소거)? 그 대가(간접 호출·할당)는 무엇인가?
//   (할당 횟수의 구체적 수치는 표준 라이브러리 구현에 따라 다를 수 있다.
//    libstdc++ 기준으로 설명하라.)

#include <cstdio>
#include <cstdlib>
#include <cstddef>
#include <functional>

static int g_allocs = 0;
void* operator new(std::size_t n) { ++g_allocs; return std::malloc(n); }
void  operator delete(void* p) noexcept { std::free(p); }
void  operator delete(void* p, std::size_t) noexcept { std::free(p); }

struct Tracer {
    static int copies;
    Tracer() = default;
    Tracer(const Tracer&) { ++copies; }
};
int Tracer::copies = 0;

struct Big { char data[64]; };

int main() {
    int b;

    b = g_allocs;
    std::function<int()> f1 = []() { return 1; };            // 무캡처
    std::printf("f1 (no capture)   allocs=%d\n", g_allocs - b);

    b = g_allocs;
    int x = 7;
    std::function<int()> f2 = [x]() { return x; };           // 작은 캡처(int)
    std::printf("f2 (int capture)  allocs=%d\n", g_allocs - b);

    b = g_allocs;
    Big big{};
    std::function<int()> f3 = [big]() { return (int)big.data[0]; }; // 큰 캡처(64B)
    std::printf("f3 (64B capture)  allocs=%d\n", g_allocs - b);

    Tracer t;
    std::function<int()> g = [t]() { return 0; };            // Tracer 캡처
    int cb = Tracer::copies;
    std::function<int()> g2 = g;                              // 함수 객체 복사
    std::printf("copy std::function -> Tracer copies=%d\n", Tracer::copies - cb);

    return 0;
}
내 리뷰 · C++
내 답안 · 자동 저장

작성 후 위 해설 보기에서 모범 해설과 대조하세요.