Post

(C++) lambda (bind 보다는 lambda를 쓰자)

lambda가 왜 필요하냐면,

함수 포인터는 함수이기 때문에 상태를 가질 수 없다. 반복문 같은데 인자로 함수를 넘겨야 하는 상황에서 그 함수가 상태를 가져야 하는 경우, 함수 포인터를 사용할 수 없다. 따라서 상태를 가지는 함수를 만들기 위해서 함수 객체라는 트릭을 사용해야 했다. 함수 객체는 객체인데, operator()를 오버로드해서 함수처럼 호출할 수 있는 객체를 의미한다. Function object는 보통 Functor라고 부른다.

1
2
3
4
5
6
struct Functor {
void operator()() {
std::cout << "Functor" << std::endl;
}
};

이렇게 별도의 Functor를 정의해주는 것이 번거롭고 지저분하다.

그래서 상태를 가지는 함수를 표현하기 위해 lambda를 사용한다.

1
2
3
[captures](parameters) { body }
// mutable, exception, trailing-return-type 지정도 가능하나 선택 사항이다.

캡쳐할 변수를 []에 지정하면 된다. 기본은 값 캡쳐이며 &var를 지정하면 참조 캡쳐다. 캡쳐할 변수를 명시적으로 지정하지 않아도, 바디에서 쓰이는 변수가 자동으로 캡쳐되도록 할 수 있다.

  • [&] 바디에서 쓰이는 모든 변수, 상수를 참조로 캡쳐하고 현재 객체 참조 캡쳐.
  • [=] 바디에서 쓰이는 모든 변수, 상수를 값으로 캡쳐하고 현재 객체 참조 캡쳐.
  • [] 아무것도 캡처하지 않음.
  • [this] 현재 객체 참조 캡쳐 * 참고. std::bind()에서 참조 캡쳐(?)처럼 사용하려면 cref(variable)를 사용한다. 보통 bind 보다는 그냥 람다 쓰는게 좋다.

다음과 같이 람다 내부에서 외부에 있는 변수의 값을 수정할 수 있다.

1
2
3
4
5
int i = 0;
auto f = [&i](std::string name) {std::cout << ++i << name << '\n';  };
f("umbum");
std::cout << i << '\n';

1
2
3
4
1umbum
1

그런데, 람다 내부에서 외부에 있는 변수를 참조가 아니라 값으로 받아와 수정하고 싶다면 mutable을 붙여주어야 한다.

1
2
3
4
5
int i = 0;
auto f = [i](std::string name) mutable {std::cout << ++i << name << '\n';  };
f("umbum");
std::cout << i << '\n';

1
2
3
4
1umbum
0

this를 넣으면 현재 객체를 참조 캡쳐할 수 있다. lambda는 friend 함수로, private로 지정된 멤버에도 접근 가능하다.

1
2
[this]() { std::cout << "cls name : " << name << std::endl; }();

bind와 bind1st는 호환이 되긴 된다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include <functional>
#include <boost/iterator/transform\_iterator.hpp>
void bind\_test() {
int x[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
const int N = sizeof(x) / sizeof(int);

typedef std::\_Binder<std::\_Unforced, std::multiplies<int>, int, const std::\_Ph<1> & > Function;
typedef boost::transform\_iterator<Function, int\*> doubling\_iterator;

using namespace std::placeholders;
auto bind\_f = std::bind(std::multiplies<int>(), 2, \_1);
doubling\_iterator i(x, bind\_f);
doubling\_iterator i\_end(x + N, bind\_f);

std::cout << "multiplying the array by 2:" << std::endl;
while (i != i\_end)
std::cout << \*i++ << " ";
std::cout << std::endl;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void bind1st\_test() {
int x[] = { 1, 2, 3, 4, 5, 6, 7, 8 };
const int N = sizeof(x) / sizeof(int);

typedef std::binder1st< std::multiplies<int> > Function;
typedef boost::transform\_iterator<Function, int\*> doubling\_iterator;

auto bind1st\_f = std::bind1st(std::multiplies<int>(), 2);
doubling\_iterator i(x, bind1st\_f);
doubling\_iterator i\_end(x + N, bind1st\_f);

std::cout << "multiplying the array by 2:" << std::endl;
while (i != i\_end)
std::cout << \*i++ << " ";
std::cout << std::endl;
}

근데 bind1st(), binder1st 같은 경우 C++11에서 deprecated 되었고, 그냥 bind()는 람다의 한계(템플릿으로 여러 타입 받기, move capture)로 아직까지는 사용하나 이런 케이스가 아니라면 그냥 람다를 사용하는 편이 좋다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
void lambda\_test() {

int x[] = { 1, 2, 3, 4, 5, 6, 7, 8 };

const int N = sizeof(x) / sizeof(int);

  


typedef boost::transform\_iterator<std::function<int(int)>, int\*> doubling\_iterator;




std::function<int(int)> doubling\_lambda = [](int x) { return x \* 2; };  // or just auto.

doubling\_iterator i(x, doubling\_lambda);

doubling\_iterator i\_end(x + N, doubling\_lambda);

  


std::cout << "multiplying the array by 2:" << std::endl;

while (i != i\_end)

std::cout << \*i++ << " ";

std::cout << std::endl;
}

This post is licensed under CC BY 4.0 by the author.