leesangwon0114

I am Research Engineer. Currently working in KT.

C++Intermediate 31. Capturing Variable

05 Dec 2018 » c++

codenuri 강석민 강사 강의 내용기반으로 정리한 내용입니다.


Capture Variable #1

#include <iostream>
using namespace std;

int g = 10;

int main()
{
    int v1 = 0, v2 = 0;

    auto f1 = [](int a) { g=0; return a + g; }; // ok. 전역변수 접근 가능
                // class ClosureType{};

    auto f2 = [](int a) { return a + v1; }; // error. 지역변수 접근 불가능
    auto f3 = [v1](int a) { return a + v1; }; // ok. 지역변수 capture를 통해 접근 가능
    auto f4 = [v1, v2](int a) { return a + v1 + v2; }; // ok.

    auto f5 = [v1](int a) { v1 = 10; return a + v1; }; // error. 지역변수 변경은 안됨

    auto f6 = [v1](int a) mutable { v1 = 10; return a + v1; }; // ok. but 복사본이 변경되는 것임
}

#include <iostream>
using namespace std;

int main()
{
    int v1 = 0, v2 = 0;

    // capture
    auto f0 = []() { return 0; };
    auto f1 = [v1, v2]() { return v1 + v2; };

    // 람다표현식은 함수객체이므로 크기를 구할 수 있음

    cout << sizeof(f0) << endl; // 1
    cout << sizeof(f1) << endl; // 8 -> 지역변수 캡쳐 시 내부에 지역변수 생김
}

지역변수를 캡쳐했을 때 값을 변경할 수 있는지 여부를 살펴보자.

#include <iostream>
using namespace std;

int main()
{
    int v1 = 0, v2 = 0;
    auto f1 = [v1, v2]() { v1 = 10; v2 = 20 }; // error
    // 괄호 연산자가 상수 멤버함수 이므로 값을 변경할 수 없음

    // mutable lambda : () 연산자 함수를 비상수 함수로
    auto f2 = [v1, v2]() mutable { v1 = 10; v2 = 20 }; // 지역변수를 보내서 멤버 데이터에 옮긴 복사본임

    f2(); // 람다 표현식 실행...
    // 값을 변경했지만 여전히 0이 출력됨
    cout << v1 << endl; // 0
    cout << v2 << endl; // 0
}

reference 캡쳐를 통해 참조로 보낼 수 있음

#include <iostream>
using namespace std;

int main()
{
    int v1 = 0, v2 = 0;
    
    // capture by value, capture by copy
    auto f1 = [v1, v2]() mutable { v1 = 10; v2 = 20 };

    f1();

    cout << v1 << endl; // 0
    cout << v2 << endl; // 0

    // capture by reference
    auto f2 = [&v1, &v2]() { v1 = 10; v2 = 20 }; // mutable 뺌(참조가 가르키는 곳은 수정 가능)

    f2();

    cout << v1 << endl; // 10
    cout << v2 << endl; // 20
}

Capture Variable #2

int main()
{
    int v1 = 0, v2 = 0, v3 = 0;

    auto f1 = [v1]() {};
    auto f2 = [&v1]() {};
    auto f3 = [v1, &v2]() {};

    // default capture
    auto f4 = [=]() {}; // capture by copy
    auto f5 = [=]() {}; // capture by reference
    auto f6 = [=, &v1]() {}; // 모든 데이터를 copy로 하는데 v1만 참조로 하겠다
    auto f7 = [&, v1] () {}; // 모든 데이터를 참조로 capture 할 건데 v1 만 값으로 하겠다.
    // auto f8 = [=, v1]() {}; // error

    auto f9 = [x=v1, v2=v2, v3] () {}; // 멤버데이터 x 에 v1 복사
    auto f10 = [v1, y=v2, &r=v3] () {};

    string s = "hello";
    auto f11 = [s1 = move(s)] () {};
    f11();
    cout << s << endl; // empty string

    unique_ptr<int> p (new int); // unique_ptr 은 복사 불가능 하니까 move로줌
    auto f12 = [p=move(p)]() {};
}

void foo(int a, int b)
{
    // auto f = []() {return a + b;}
    auto f = [a, b]() {return a + b;} // ok.
    auto f = [=]() {return a + b;} // ok.
}

template<typename ... Types> void goo(Types ... args)
{
    auto f1 = [=]() { auto t = make_tuple(args...); };
    auto f2 = [args...]() { auto t = make_tuple(args...); };
}

멤버데이터의 Capture

class Test
{
    int data = 0;
public:
    void foo() // Test* const this
    {
        int v = 0;
        // auto f = [this]() { this->data = 10; }; // this 이용
        auto f = [this]() { data = 10; }; // this 생략도 가능

        // 멤버 데이터를 복사본으로 캡쳐 - C++17
        auto f2 = [*this]() mutable { this->data = 10; };

        f();

        cout << data << endl; // 10
    }
};

int main()
{
    Test t;
    t.foo();
}

Conversion

람다표현식과 함수포인터로의 변환

class ClosureType
{
public:
    int operator()(int a, int b) const
    {
        return a + b;
    }

    static int method(int a, int b)
    {
        return a + b;
    }
    
    
    typedef int(*F)(int, int);

    operator F()
    {
        // return &ClosureType::operator(); // 멤버함수는 불가 -> 일반함수 필요(static 하나 더 만듬)
        return &ClosureType::method;
    }
}

int main()
{
    int(*f)(int, int) = [](int a, int b)
    {
        return a + b;
    }
}

괄호연산자는 static 이 될 수 없으므로 별도의 static method 가 생성되어 리턴시킴

Capture 할 때 문제가 됨

class ClosureType
{
public:
    ClosureType(int a) : v(a) {}
    int operator()(int a, int b) const
    {
        return a + b + v;
    }

    static int method(int a, int b)
    {
        return a + b + v; ? error
    }
    
    
    typedef int(*F)(int, int);

    operator F()
    {
        // return &ClosureType::operator(); // 멤버함수는 불가 -> 일반함수 필요(static 하나 더 만듬)
        return &ClosureType::method;
    }
}

int main()
{
    int(*f)(int, int) = [](int a, int b)
    {
        return a + b;
    }

    int v = 0;

    // capture 구문을 사용하면 함수포인터로 암시적 변환 될 수 없다.
    int(*f)(int, int) = [v](int a, int b)
    {
        return a + b + v;
    }
}

따라서 Capture 하지 않은 함수포인터만 암시적 변환이 됨


Lambda MISC

#include <iostream>
using namespace std;

// g++ 만 지원되고 있음(Concept Ts 내용)
// void foo(auto n) {}

int main()
{
    // generic lambda : C++14
    auto f1 = [](auto a, auto b) { return a + b; };
    cout << f1(1, 2.1) << endl;

    auto f2 = [](int a) { return 10; };
    // nullary lambda
    auto f4 = [] { return 10; }; // 인자가 없을 때 () 빼도됨

    // C++17 : () 함수를 constexpr 함수로...(인자로 liternal이 오면 컴파일 시간에 대입시킴)
    auto f3 = [](int a, int b) constexpr
    {
        return a + b;
    };

    int y[f3(1,2)]; // 컴파일 시간에 계산되면 가능
}

#include <iostream>
using namespace std;

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

    decltype(f1) f2; // error. default 생성자가 삭제되어 있어서 error

    decltype(f1) f3 = f1; // ok. 복사생성자는 기본버전이 제공되 문제 없음

    decltype(f1) f4 = move(f1); // ok. Move 생성자도 기본버전이 제공됨
}