일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
Tags
- UE4
- Effective c++
- resource management class
- implicit conversion
- 반복자
- more effective c++
- exception
- effective stl
- 티스토리챌린지
- 암시적 변환
- 언리얼
- iterator
- 게임
- effective modern c++
- universal reference
- virtual function
- reference
- 스마트 포인터
- 예외
- lua
- std::async
- c++
- 오블완
- 보편 참조
- 영화
- operator new
- 상속
- 영화 리뷰
- 참조자
- Smart Pointer
Archives
- Today
- Total
스토리텔링 개발자
[Effective Modern C++] 34. std::bind 대신 람다 본문
728x90
항목 34. std::bind보다 람다를 선호하라
이유 1 : 가독성
- 람다가 가독성이 더 좋다.
using Time = std::chrono::steady_clock::time_point;
enum class Sound { Beep, Siren, Whistle };
using Duration = std::chrono::steady_clock::duration;
// t 시간에 s 사운드를 d 동안 출력하라
void setAlarm(Time t, Sound s, Duration d);
- 람다 버전
// 한시간 후부터 30초간 울리게 하는 함수 객체
auto setSoundL = [](Sound s)
{
using namespace std::chrono;
setAlarm(steady_clock::now() + hours(1), s, seconds(30));
};
// C++14버전에서는 리터럴 suffix를 이용해서 더 줄일 수 있다.
auto setSoundL = [](Sound s)
{
using namespace std::chrono;
using namespace std::literals;
setAlarm(steady_clock::now() + 1h, s, 30s);
};
- 바인드 버전
// std::bind를 이용해서 작성한 버전
using namespace std::chrono;
using namespace std::literals;
using namespace std::placeholders; // _1을 사용하기 위해
// 허나 문제가 있다!
auto setSoundB = std::bind(setAlarm, steady_clock::now() + 1h, _1, 30s);
- steady_clock::now() 가 실행 시점이 아니라 바인딩 되는 시점에 호출된다.
- 결과적으로, 경보는 setAlarm 호출 후 한시간이 지난 시점이 아니라
- std::bind 호출 후 한시간이 지난 시점에 울린다.
- 해결하려면 setAlarm 호출 때까지 지연시키라고 std::bind에게 알려줘야 한다.
audo setSoundB = std::bind(setAlarm,
std::bind(std::plus<>(),
std::bind(steady_clock::now),
1h),
_1,
30s);
- std::plus<>
- 타입이 들어가지 않았다.
- C++14에서는 표준 연산자 템플릿에 대한 템플릿 타입 인수를 생략할 수 있다.
- C++11은 즉 아래처럼 구현해야 한다.
audo setSoundB = std::bind(setAlarm,
std::bind(std::plus<steady_clock::time_point>(),
std::bind(steady_clock::now),
1h),
_1,
30s);
- 무엇이 가독성이 좋은지는... 자명하다.
이유 2 : 오버로드
- 음량을 네 번째 매개변수로 받는 버전을 추가한다고 하면?
enum class Volume { Normal, Loud, LoudPlusPlus };
void setAlarm(Time t, Sound s, Duration d, Volume v);
- 람다는 문제없이 동작한다.
- 인수 세 개짜리 버전을 알아서 선택하여 호출하기 때문이다.
- std::bind 버전은 컴파일되지 않는다.
- 컴파일러는 두 버전 중 어떤 것을 std::bind에 넘겨주어야 할지 결정할 방법이 없다.
- 컴파일러는 함수 이름밖에 모르기 때문이다.
- 이를 해결하기 위해서는 적절한 함수 포인터 타입으로 캐스팅해야 한다.
using SetAlarm3ParamType = void(*)(Time t, Sound s, Duration d);
// 캐스팅 추가
auto setSoundB = std::bind(static_cast<SetAlarm3ParamType>(setAlarm),
std::bind(std::plus<>(),
std::bind(steady_clock::now),
1h),
_1,
30s);
좀 더 복잡한 코드
// C++14 버전
audo betweenL = [lowVal, highVal](const auto& val)
{
return lowVal <= val && val <= highVal;
};
// C++11 버전
audo betweenL = [lowVal, highVal](int val)
{
return lowVal <= val && val <= highVal;
};
- std::bind 로도 표현은 가능하지만, 이해가 너무 힘들다.
using namespace std::placeholders;
// C++14 버전
audo betweenB = std::bind(std::logical_and<>(),
std::bind(std::less_equal<>(), lowVal, _1),
std::bind(std::less_equal<>(), _1, highVal));
// C++11 버전
audo betweenB = std::bind(std::logical_and<bool>(),
std::bind(std::less_equal<int>(), lowVal, _1),
std::bind(std::less_equal<int>(), _1, highVal));
이유 3 : 캡쳐 방식 가독성
enum class CompLevel { Low, Normal, High };
Widget Compress(const Widget& w, CompLevel lev);
- std::bind 버전
Widget w;
// 람다 버전
// w는 명백히 값으로 캡쳐된다.
auto compressRateL = [w](CompLevel lev) { return compress(w, lev); };
// std::bind 버전
using namespace std::placeholders;
// 여기서 w는 어떻게 저장될까?(값? 참조?)
auto compressRateB = std::bind(compress, w, _1);
- std::bind 버전에서, w는 값으로 전달된다.
- 하지만 이를 알기 위해서는 반드시 std::bind의 작동 방식을 꿰고 있어야 한다.
- 호출 구문 자체로는 그 사실을 추론할 수 없다.
- 또한 호출 시 인수를 전달할 때에도 가독성 문제가 있다.
// 람다 버전
compressRateL(CompLevel::High); // 인수는 명확히 값으로 전달된다.
// 명세 '(CompLevel lev)'를 보면 명확히 값임을 알 수 있다.
// std::bind 버전
compressRateB(CompLevel::High); // 어떻게 전달될까??
- std::bind 버전에서 인수는 참조로 전달된다.
- 함수 호출 연산자가 완벽 전달을 사용하기 때문이다.
- 이 역시 알기 위해서는 std::bind의 동작 방식을 꿰고 있어야 한다.
std::bind가 유용한 경우
- 이동 캡쳐(move capture)
- C++11 람다는 이동 캡쳐를 지원하지 않는다.
- 이를 흉내낼 때 사용할 수 있다.(항목 32 참조)
- 다형적 함수 객체(polymorphic function object)
- 바인드 객체에 대한 함수 호출 연산자는 완벽 전달을 사용한다.
- 그러므로 어떤 타입의 인수도 받을 수 있다.(항목 30의 완벽 전달 제약 안에서)
- 즉, 객체를 템플릿화된 함수 호출 연산자와 묶으려 할 때 유용하다.
- 이 역시 C++11에서만 유용하다.
class PolyWidget
{
public:
template<typename T>
void operator()(const T& param) const;
...
};
// 이렇게 묶으면 어떤 타입도 받을 수 있게 된다.
PolyWidget pw;
auto boundPW = std::bind(pw, _1);
boundPW(1930); // int 전달
boundPW(nullptr); // nullptr 전달
boundPW("Rosebud"); // 문자열 전달
- C++14에서는 매개변수로 auto를 사용할 수 있으므로 훨씬 간단하다.
auto boundPW = [pw](const auto& param) { pw(param); };
728x90
'Effective C++ > Effective Modern C++' 카테고리의 다른 글
[Effective Modern C++] 36. std::async의 시동 방침(launch policy) (0) | 2025.04.10 |
---|---|
[Effective Modern C++] 35. 태스크 기반 프로그래밍(task-based programming) (0) | 2025.04.09 |
[Effective Modern C++] 33. auto&& 매개변수를 std::forward로 전달할 땐 decltype (0) | 2025.04.04 |
[Effective Modern C++] 32. 람다 초기화 캡쳐(init capture) (0) | 2025.04.03 |
[Effective Modern C++] 31. 람다 기본 캡쳐 모드 지양하기 (0) | 2025.04.01 |
Comments