Lambda
1
std::find_if(container.begin(), container.end(), [](int val){ return 0 < val && val < 10; });
- 람다는 함수 객체를 만드는데 유용한 방법
- 람다 표현식은 단순한 표현식
- 소스 코드의 일부
[](...){~}
- 클로저(closure)
- 람다에 의해 런타임에 생성되는 객체
- 캡쳐 모드에 따라 클로져는 캡쳐된 데이터의 복사본 또는 레퍼런스를 갖고 있게됨
- 복사 가능 (
auto c1 = [x](int y){return x > 55;});
)
- 클로져 클래스(closure class)
- 클로져가 인스턴스화된 클래스
- 각각의 람다는 컴파일러가 고유한 클로져 클래스를 만들게 함
- 람다 표현식 내부의 문장(statement)들은 그 람다 표현식으로 인해 생성되는 클로져 클래스의 멤버 함수 속의 실행가능한 명령문(instruction)이 됨
- 람다 표현식은 단순한 표현식
항목31: 기본 캡쳐 모드(default capture mode)를 피하라
- C++11의 람다의 캡쳐모드 두 종류는 잠재적인 문제점을 갖고 있음
- by-reference
- by-value
by-reference
- 댕글링 위험
- 람다가 정의된 범위에서 사용가능한 지역, 매개 변수들에 대한 레퍼런스를 포함하는 클로져를 생성함
- 클로져의 생명주기가 지역변수나 매개변수보다 더 길다면, 레퍼런스가 댕글링을 일으킴
ex) 필터함수
1
2
3
4
5
using FilterContainer = std::vector<std::function<bool(int)>>;
FilterContainer filters;
filters.emplace_back([](int value){ return value % 5 == 0; });
- 특정 변수로 체크하는 경우는 아래와 같다.
1
2
3
4
5
6
7
8
9
10
11
12
void addDivisorFilter()
{
auto calc1 = computeSomeValue1();
auto calc2 = computeSomeValue2();
auto divisor = computeDivisor(calc1, calc2);
filters.emplace_back
(
[&](int value) { return value % divisor == 0; }
);
}
[&]
에 의해 지역 변수 divisor의 레퍼런스가 클로져에 저장됨- filters의 생명주기가 더 길기 때문에 문제됨
by-value
- 댕글링 문제해결할 방안?
1
2
filters.emplace_back
([=](int value){ return value % divisor == 0; });
- 그러나, 댕글링 문제에서 벗어날 수 없음
- 포인터 변수는 주소값을 가지므로, 이를 참조할 때 문제가 생김 (대표적 예: this)
1
2
3
4
5
6
7
8
9
10
void Widget::addFilter() const
{
auto currentObjectPtr = this;
filters.emplace_back
([currentObjectPtr](int value)
{
return value % currentObjectPtr->divisor == 0;
});
}
[=]
- 해당 람다가 생성된 범위에서 볼 수 있는 static이 아닌 지역변수를 복사 (매개변수 포함)
- 클래스 멤버 변수들은 캡쳐 못함
this
를 사용하여 멤버변수에 접근해야함
- 해당 람다가 생성된 범위에서 볼 수 있는 static이 아닌 지역변수를 복사 (매개변수 포함)
원하는 멤버변수를 this 대신 지역변수에 담아 복사하면됨
- C++14의 일반화된 람다 캡쳐(
[divisor = divisor]
)
- C++14의 일반화된 람다 캡쳐(
by-value 문제점
해당 클로져가 독립적이고, 외부 데이터의 변화로부터 어떤 영향도 받지 않을 것처럼 보이기 때문
사실은 “지역변수”, “매개변수”, “정적 객체(static object)”들로부터도 영향을 받음
- 외부에 완전히 독립적이지 않음
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void addDivisorFilter()
{
static auto calc1 = computeSomeValue1();
static auto calc2 = computeSomeValue2();
static auto divisor =
computeDivisor(calc1, calc2);
filters.emplace_back(
[=](int value)
{ return value % divisor == 0; }
);
++divisor;
}
- 정적 변수
- 캡쳐가 아니라 외부의값을 그저 가져다 쓰는것
- divisor 값이 변하면 람다 내부의 결과도 변함
항목32: 객체를 클로저 안으로 이동하려면 init capture를 사용하라
- move-only(unique_ptr, future) 객체를 클로져 내부로 이동시키고 싶은 경우
- C++11에서는 방법이 없음
- C++14에서는 클로져 내부로 객체를 이동시키는 방법을 제공함
C++14: init capture
- init capture
- 외부의 객체를 클로져 내부로 이동시킴
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Widget
{
public:
bool isValidated() const;
bool isProcessed() const;
bool isArchived() const;
private:
};
auto pw = std::make_unique<Widget>();
//closure 내부의 멤버 pw를 std::move(pw)로 초기화
auto func = [pw = std::move(pw)]
{ return pw->isValidated()
&& pw->isArchived(); };
사용
- 람다로부터 생성되는 클로져 클래스 내부의 데이터 멤버 이름 기술
- 해당 데이터 멤버를 초기화하기 위한 표현식(expression) 기술
[
클로져 클래스 내부의 멤버 이름
=내부 멤버를 초기화하기 위한 표현식
]- 내부 멤버 초기화 가능
C++11
직접 함수 객체를 구현 (Functor)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class IsValAndArch
{
public:
using DataType = sstd::unique_ptr<Widget>;
explicit IsValAndArch(DataType&& ptr)
: pw(std::move(ptr)){}
bool operator()() const
{ return pw->isValidated() && pw->isArchived(); }
private:
DataType pw;
};
auto func = IsValAndArch(std::make_unique<Widget>());
std::bind 응용
- 캡쳐할 객체를 std::bind로 생성된 함수 객체로 이동
- 람다에 캡쳐된 객체에 대한 레퍼런스를 주기
1
2
3
4
5
6
7
8
9
10
11
// C++14의 경우
std::vector<double> data;
auto func = [data = std::move(data)]{};
// C++11의 경우
std::vector<double> data;
auto func =
std::bind(
[](const std::vector<double>& data)
{ }, std::move(data));
std::bind
- 이 역시 함수 객체를 리턴
- 1번째 인자: 호출 가능한 오브젝트
- 2번째 인자: 해당 오브젝트로 전달될 값들을 나타냄
람다 표현식으로 인해 클로저 클래스 내부의 operator() 함수는 기본적으로 const
- 클로저 내부의 모든 데이터 멤버들은 람다의 body에서 const로 취급
- C++11의 방식인 bind object안에 이동 생성된 data의 복사본은 const가 아님
람다를 mutable로 선언하면, const 제거 가능
1
2
3
4
auto func =
std::bind(
[](std::vector<double>& data) mutable
{ }, std::move(data));
- std::bind 장점: 댕글링 발생 x
- std::bind는 자신의 인자로 넘어온 모든 인자들을 bind object에 저장
- 클로져도 내부적으로 저장
- 클로져의 생명주기와 bind object와 같아짐
1
2
3
4
5
6
7
8
9
10
11
//C++14
auto func = [pw = std::make_unique<Widget>()]
{ return pw->isValidated()
&& pw->isArchived(); };
//C++11
auto func = std::bind(
[](const std::unique_ptr<Widget>& pw)
{return pw->isValidated()
&& pw->isArchived(); },
std::make_unique<Widget>());
항목33: std::forward를 통해서 전달할 auto&& 매개변수에는 decltype을 사용하라
C++14의 generic lambda 기능
- 람다의 매개변수 정의에 auto를 사용가능
- 이 기능의 구현: 람다의 클로저 클래스 내부 멤버함수
operator()
를 템플릿으로 만드는 것으로 이루어짐
- 이 기능의 구현: 람다의 클로저 클래스 내부 멤버함수
- 람다의 매개변수 정의에 auto를 사용가능
https://jwvg0425.tistory.com/50
항목34: std::bind보다 람다를 선호하라
- https://jwvg0425.tistory.com/51
출처
- https://jwvg0425.tistory.com/48
- https://jwvg0425.tistory.com/49