Skip to main content

[C++ Primer Plus] Chapter 18. (2) 람다 함수, 래퍼, 가변인자 템플릿

C++ 기초 플러스 책을 읽고 공부한 노트입니다.




람다 함수 #

  • 랜덤한 정수들 중에서 얼마나 많은 수가 3으로, 13으로 나뉘는지 확인하고 싶다.

1. 함수 포인터

bool F3(int x) { return (x % 3) == 0; }
bool F13(int x) { return (x % 13) == 0; }

int main()
{
    vector<int> numbers(1000);
    generate(numbers.begin(), numbers.end(), rand);

    int count3 = count_if(numbers.begin(), numbers.end(), F3);
    int count13 = count_if(numbers.begin(), numbers.end(), F13);
}

2. 펑크터

class FMod
{
private:
    int dv;

public:
    FMod(int d = 1) : dv(d) {}
    bool operator()(int x) { return (x % dv) == 0; }
};


int main()
{
    vector<int> numbers(1000);
    generate(numbers.begin(), numbers.end(), rand);

    int count3 = count_if(numbers.begin(), numbers.end(), FMod(3));
    int count13 = count_if(numbers.begin(), numbers.end(), FMod(13));
}

3. 람다

  • 함수 이름이 []이 되었다.
  • 반환 타입은 선언되지 않았다. decltype이 반환 값으로부터 추정된 타입이 반환 타입이 된다.
    • 만약, 단일 구문을 반환하는 구조가 아니라서 반환 타입이 자동으로 결정되지 않는다면 반환 값을 추정하는 문법이 필요하다.
int main()
{
    vector<int> numbers(1000);
    generate(numbers.begin(), numbers.end(), rand);

    int count3 = count_if(numbers.begin(), numbers.end(), [](int x){ return (x % 3) == 0; });
    int count13 = count_if(numbers.begin(), numbers.end(), [](int x){ return (x % 13) == 0; });
}
[](int i)->double { double d = i; return d - i; }
// 반환 타입은 double형이다. 

  • 왜 람다를 쓰는가?
    • 근접성
      • 사용하는 곳 가까이에 정의하는 것이 유용하다. count_if() 함수 세 번째 매개변수가 호출하는 것이 무엇인지 코드를 스캔하고 싶지 않다.
    • 간결함
      • 람다에 이름을 생성하여 반복해 사용할 수 있다.
      • auto mod3 = [](int x){ return (x % 3) == 0; };
    • 효율
      • 인라인화가 되어 접근이 빠르다.
    • 능력
      • 범위 내에서 모든 자동화된 변수 이름으로 접근이 가능하다.
        • [변수이름]으로 변수를 값으로 접근한다.
        • [&변수이름]으로 변수를 참조로 접근한다.
        • [&]으로 모든 자동화된 변수를 참조로 접근한다.
        • [=]으로 모든 자동화된 변수를 값으로 접근한다.
        • 혼합하여 사용하는 것도 가능하다.
int count3 = 0;
for_each(numbers.begin(), numbers.end(), [&count3](int x){ count3 += ((x % 3) == 0); });
int count3 = count13 = 0;
for_each(numbers.begin(), numbers.end(), 
    [&](int x){ count3 += ((x % 3) == 0); count13 += ((x % 13) == 0); });



래퍼(Wrapper) #

  • 템플릿의 비효율성
    • 많은 타입이 존재할 수 있는 가능성은 템플릿의 비효율성을 초래할 수 있다.
    • 아래 예제와 같은 경우 F의 시그니처는 double (double)로 동일함에도 불구하고, UseFunction()의 인스턴스가 5개나 생성되었다.
template <typename T, typename F>
T UseFunction(T v, F f)
{
    static int count = 0;  // static 변수로 얼마나 많은 인스턴스가 생성되는지 체크한다. 
    count++;
    cout << "UseFunction count = " << count << ", &count = " << &count << endl;
    return f(v);
}

class Fp
{
private:
    double z;

public:
    Fp(double zz = 1.0) : z(zz) {}
    double operator()(double p) { return z * p; }
};

class Fq
{
private:
    double z;

public:
    Fq(double zz = 1.0) : z(zz) {}
    double operator()(double q) { return z + q; }
};

double Double(double x)
{
    return 2.0 * x;
}

double Square(double x)
{
    return x * x;
}

int main()
{
    double y = 1.21;

    cout << "함수 포인터 Double:\n";  // double (*) (double)
    cout << "  " << UseFunction(y, Double) << endl << endl;

    cout << "함수 포인터 Square:\n";  // double (*) (double)로 같다.
    cout << "  " << UseFunction(y, Square) << endl << endl;

    cout << "펑크터 Fp:\n";          // Fp의 인스턴스
    cout << "  " << UseFunction(y, Fp(5.0)) << endl << endl;

    cout << "펑크터 Fq:\n";          // Fq의 인스턴스
    cout << "  " << UseFunction(y, Fq(5.0)) << endl << endl;

    cout << "람다 표현식 1:\n";
    cout << "  " << UseFunction(y, [](double u){return u * u; }) << endl << endl;

    cout << "람다 표현식 2:\n";
    cout << "  " << UseFunction(y, [](double u){return u + u / 2.0; }) << endl << endl;
}
함수 포인터 Double:
UseFunction count = 1, &count = 001E03D8
  2.42

함수 포인터 Square:
UseFunction count = 2, &count = 001E03D8
  1.4641

펑크터 Fp:
UseFunction count = 1, &count = 001E03DC
  6.05

펑크터 Fq:
UseFunction count = 1, &count = 001E03E0
  6.21

람다 표현식 1:
UseFunction count = 1, &count = 001E03E4
  1.4641

람다 표현식 2:
UseFunction count = 1, &count = 001E03E8
  1.815

  • 함수 래퍼로 문제 해결
    • function<>템플릿은 같은 함수 시그내처를 갖는 함수 포인터, 펑크터, 람다 표현식을 포장하는데 사용한다.
    • 아래 코드와 같은 경우, function<double(double)>을 사용하여 여섯 개의 래퍼를 생성한다.
    • 이것은 F를 모두 같은 타입으로 만든다. 따라서 최종적으로 1개의 UseFunction()인스턴스만 만들어진다.
double y = 1.21;

// 6개의 래퍼를 생성한다. 
function<double(double)> ef1 = Double;
function<double(double)> ef2 = Square;
function<double(double)> ef3 = Fq(5.0);
function<double(double)> ef4 = Fp(5.0);
function<double(double)> ef5 = [](double u){ return u * u; };
function<double(double)> ef6 = [](double u){ return u + u / 2.0; };

cout << "함수 포인터 Double:\n";
cout << "  " << UseFunction(y, ef1) << endl << endl;

cout << "함수 포인터 Square:\n";
cout << "  " << UseFunction(y, ef2) << endl << endl;

cout << "펑크터 Fp:\n";
cout << "  " << UseFunction(y, ef3) << endl << endl;

cout << "펑크터 Fq:\n";
cout << "  " << UseFunction(y, ef4) << endl << endl;

cout << "람다 표현식 1:\n";
cout << "  " << UseFunction(y, ef5) << endl << endl;

cout << "람다 표현식 2:\n";
cout << "  " << UseFunction(y, ef6) << endl << endl;
함수 포인터 Double:
UseFunction count = 1, &count = 00945660
  2.42

함수 포인터 Square:
UseFunction count = 2, &count = 00945660
  1.4641

펑크터 Fp:
UseFunction count = 3, &count = 00945660
  6.21

펑크터 Fq:
UseFunction count = 4, &count = 00945660
  6.05

람다 표현식 1:
UseFunction count = 5, &count = 00945660
  1.4641

람다 표현식 2:
UseFunction count = 6, &count = 00945660
  1.815

  • typedef로 간소화한 표현이 가능하다.
double y = 1.21;

typedef function<double(double)> fdd;

//...

cout << "  " << UseFunction(y, fdd(Double)) << endl << endl;

  • 템플릿 자체를 바꾸어 볼 수도 있다.
template <typename T>
T UseFunction(T v, function<T(T)> f) 
{
    static int count = 0; 
    count++;
    cout << "UseFunction count = " << count << ", &count = " << &count << endl;
    return f(v);
}



가변인자 템플릿 #

  • 임의의 개수의 인자를 받는 함수를 만들 수는 없을까?

  • 템플릿 매개변수 팩(Template parameter packs), 함수 매개변수 팩(Function parameter packs)
// ...는 0개 이상의 인자들을 나타낸다. 
template<typename... Args>  // 템플릿 매개변수 팩
void Show(Args... args)     // 함수 매개변수 팩
{
}


int main()
{
    Show();
    Show("hello");
    Show(4.5, 1, string("str"), 'c');
}

  • 언패킹 팩(Unpacking a pack)
    • 어떻게 팩의 내용을 언패킹할 수 있을까?
    • 즉, Show()함수 내에서 어떻게 팩의 내용인 4.5, 1, string("str"), c들을 접근할 수 있을까?

  • 재귀(Recursion)
    • 재귀를 적절하게 사용하면 팩의 아이템들에 접근할 수 있다.

template <typename T>
void Show(T arg)  // 마지막 매개변수에 대한 처리.
{
    cout << arg << endl;
}

template <typename T, typename... Args>
void Show(T arg, Args... args)
{
    cout << arg << ", ";
    Show(args...);  // 첫번째 매개변수를 제외한 매개변수로 재귀적으로 호출한다. 
}


int main()
{
    int n = 14;
    double x = 2.71828;
    string mr = "Mr. String objects!";

    Show(n, x);
    Show(x * x, '!', 7, mr);
}
14, 2.71828
7.38905, !, 7, Mr. String objects!