dh_0e

[C++] lambda, 타입 추론(auto), 일반화 함수(template), Singleton in C++ 본문

C++

[C++] lambda, 타입 추론(auto), 일반화 함수(template), Singleton in C++

dh_0e 2025. 8. 5. 11:24

람다(lambda) 함수

  • JavaScript(Node.js)의 화살표 함수(arrow function)와 비슷한 개념
  • 익명 함수를 정의하고 간결하게 전달하는 함수 객체
  • 내부 동작과 문법, this 캡처와 클로저 처리 방식에서 arrow function과의 차이점을 보임

C++

auto add = [](int a, int b) {
    return a + b;
};

std::cout << add(3, 4); // 7

 

Node.js (JavaScript)

const add = (a, b) => {
    return a + b;
};

console.log(add(3, 4)); // 7

 

C++ lambda vs JS arrow function

항목 C++ 람다 JS 화살표 함수
클로저(캡처) [], [=], [&] 등으로 명시적으로 지정 자동으로 외부 스코프의 변수 접근 가능
this 바인딩 명시적 캡처 필요 화살표 함수는 자신의 this를 가지지 않음, 외부 this를 캡처함
타입 추론 C++14부터 auto 또는 템플릿 사용 가능 JS는 동적 타입이므로 자유롭고 항상 가능
함수 객체 표현 std::function, 함수 객체로 처리 가능 일급 객체 (Function)로 언제든지 전달 가능
리턴 타입 명시 필요 시 -> int 등 명시 가능 명시 X, 자동 추론됨
실행 환경 컴파일 시 (정적 타입) 런타임 시 (동적 타입)

 

 

캡처(Capture)

  • 람다 함수 내부에서 바깥 변수(지역 변수 or 멤버 변수)에 접근하려면, 해당 변수를 캡처해야 함
  • 전역 변수는 캡처 없이 사용 가능
// [캡처리스트](파라미터들) { 함수 본문 };

int x = 10;

auto f = [x]() {  // x를 값으로 캡처
    std::cout << x << std::endl;
};
캡처 방식 문법 예 의미
값으로 캡처 [x] 변수 x의 값을 복사해서 사용 (람다 생성 시점)
참조로 캡처 [&x] 변수 x에 대한 참조를 저장 (원본 직접 수정 가능)
모든 변수 값으로 [=] 람다 밖에서 사용한 모든 변수를 값으로 캡처
모든 변수 참조로 [&] 람다 밖에서 사용한 모든 변수를 참조로 캡처
혼합 [=, &y] 기본은 값 캡처, y만 참조로 캡처
this 캡처 [this] 람다가 선언된 클래스의 멤버에 접근 가능
객체 전체 캡처 [=, this] 멤버 함수 내부에서 this와 외부 지역 변수 동시 캡처

 

ex) 캡처 리스트 혼합

int a = 1, b = 2, c = 3;

auto mixed = [=, &b, &c]() {
    // a는 값 복사
    // b, c는 참조
    std::cout << a << " " << b << " " << c << std::endl;
};

 

ex) 클래스 멤버와 [this]

class MyClass {
public:
    int data = 42;

    void run() {
        auto f = [this]() {
            std::cout << data << std::endl;  // this->data
        };
        f();
    }
};
  • 값 캡처는 복사본이기 때문에 람다 내에서 변경해도 원본 변수는 바뀌지 않음
  • 참조 캡처는 람다 외부에서 변경된 값이 반영
  • 기본 캡처 [=] 또는 [&]를 사용할 때도 어떤 변수가 캡처되는지 정확히 파악해야 안전함

 

mutable, constexpr, noexcept

auto f = [x]() mutable noexcept -> int {
    x += 1;
    return x;
};
키워드 의미
mutable 캡처한 변수를 내부에서 수정 가능
constexpr 컴파일 타임 실행 보장
noexcept 예외를 발생시키지 않음을 명시
-> T 반환 타입 명시

 

template, auto

 

template: 일반화 프로그래밍

template <typename T>
T add(T a, T b) {
    return a + b;
}
  • 다양한 타입에 대해 코드 재사용 가능
  • 컴파일 시 타입별로 인스턴스화됨

auto: 타입 추론

auto x = 10;       // int로 추론
auto result = add(1, 2);  // 반환값 타입 추론
  • 변수, 반복자, 람다 매개변수 등에 쓰이는 타입 생략 도구

template vs auto

항목 template auto
사용 목적 일반화된 코드 정의 타입 추론을 편하게
정의 위치 함수/클래스 정의 시 사용 변수 선언 시 사용
사용 예 여러 타입에 대해 하나의 코드 작성 타입을 몰라도 변수 선언 가능
타입 유추 시점 함수 호출 시점 컴파일러가 변수 선언 시점에 추론

 

C++11: decltype

  • 표현식 기반 타입 추
int a = 3;
decltype(a) b = 5; // b는 int
  • decltype(a + 0.5)는 double이 됨

 

C++14: 함수에서 auto 반환, generic lambda

auto 함수 반환 타입

auto add(int a, int b) {
    return a + b; // 컴파일러가 반환 타입 추론
}

Generic Lambda

auto g = [](auto a, auto b) {
    return a + b;
};

 

C++17: 구조화 바인딩과 if constexpr

구조화 바인딩(structured bindings)

tuple<int, string> t = {1, "hello"};
auto [id, name] = t;

 

if constexpr (템플릿 내부에서 조건 분기)

template<typename T>
void printType(T x) {
    if constexpr (is_integral<T>::value) {
        cout << "정수형입니다" << endl;
    } else {
        cout << "정수형이 아닙니다" << endl;
    }
}

 

C++20: auto 파라미터 + Concepts로 템플릿 대체

auto 함수 파라미터

auto multiply(auto a, auto b) {
    return a * b;
}
  • 이는 사실 아래 코드의 축약형임:
template<typename T, typename U>
auto multiply(T a, U b) {
    return a * b;
}

 

Concepts로 타입 제약 걸기

#include <concepts>

auto add(std::integral auto a, std::integral auto b) {
    return a + b;
}
  • std::integral → 정수형(int, long, short 등)만 허용
  • add(1.2, 3.4) 하면 컴파일 에러 발생
  • 코드 안정성과 명시성 증가

사용자 정의 Concept

template<typename T>
concept Addable = requires(T a, T b) {
    a + b;
};

template<Addable T>
T add(T a, T b) {
    return a + b;
}
  • requires() 안에서 해당 연산이 가능한지 검사
  • Addable이라는 이름의 concept에 정의

좀 더 복잡한 조건도 가능:

template<typename T>
concept StringLike = requires(T a) {
    { a.length() } -> std::convertible_to<size_t>;
    { a[0] } -> std::same_as<char>;
};

 

자주 사용하는 표준 concept

Concept 의미
std::integral 정수형 타입 (int, long 등)
std::floating_point 부동소수 타입 (float, double)
std::same_as<T> T와 같은 타입인지
std::convertible_to<T> T로 암시적 변환 가능한지
std::default_initializable 기본 생성 가능한지
std::copyable, std::movable 복사/이동 가능한지

 

 

Lambda + Concept (C++20)

auto intAdd = [](std::integral auto a, std::integral auto b) {
    return a + b;
};

or

auto addOnlyStrings = [](const auto& a, const auto& b)
    requires (std::same_as<decltype(a), string> && std::same_as<decltype(b), string>)
{
    return a + b;
};

addOnlyStrings("hi"s, " there"s);  // OK
addOnlyStrings(1, 2);             // ❌ 컴파일 에러
기능 C++98~03 C++11 C++14 C++17 C++20
타입 추론 없음 auto auto 반환 if constexpr, structured bindings auto 파라미터, concepts
일반화 함수 template<typename T> - auto 반환만 가능 - auto만으로 템플릿처럼 사용 가능
타입 제약 enable_if, SFINAE decltype, is_same constexpr 조건 분기 if constexpr concepts

 

Singleton in C++

  • 클래스의 인스턴스를 하나만 만들 수 있도록 제한
  • 어디서든 그 인스턴스에 전역적으로 접근 가능하게 함
  • 생성자를 private으로 막고, 정적(static) 메서드를 통해 인스턴스를 생성 및 반환

구조

class Singleton {
private:
    static Singleton* instance;
    Singleton() {}                         // 생성자 private

public:
    static Singleton* getInstance() {
        if (!instance)
            instance = new Singleton();
        return instance;
    }

    void doSomething() {
        cout << "Doing something!" << endl;
    }
};

// 정적 멤버 초기화
Singleton* Singleton::instance = nullptr;

 

사용

int main() {
    Singleton* s1 = Singleton::getInstance();
    Singleton* s2 = Singleton::getInstance();

    s1->doSomething();

    cout << (s1 == s2) << endl; // 1 (true)
}

 

C++11 이후 안전한 싱글턴 (static local 방식)

class Singleton {
private:
    Singleton() {}
public:
    static Singleton& getInstance() {
        static Singleton instance;  // C++11 이상: thread-safe 보장
        return instance;
    }

    void sayHello() {
        cout << "Hello Singleton!" << endl;
    }

    // 복사, 대입 금지
    Singleton(const Singleton&) = delete;
    void operator=(const Singleton&) = delete;
};

int main() {
    Singleton& s1 = Singleton::getInstance();
    Singleton& s2 = Singleton::getInstance();

    s1.sayHello();
    cout << std::boolalpha << (&s1 == &s2) << endl; // true
}

 

'C++' 카테고리의 다른 글

[C++] memset, isupper/islower  (0) 2025.02.13
[C++] stringstream  (2) 2024.07.22
[C++] max_element, min_element, find  (0) 2024.07.12
[C++] map, unordered_map  (0) 2024.07.03
[C++] 행렬 곱셈(matrix multiplication) 정의와 그 이유, 구현  (0) 2024.06.25