티스토리 뷰
[Effective Modern C++] 항목 34. std::bind보다 람다를 선호하라
pppgod 2020. 1. 18. 19:32std::bind 는 C++98 부터 이어져 내려온 라이브러리의 일부였다. 그러나 C++14 에 들어서며 std::bind 는 필요성이 사라졌다. 그 이유를 살펴보도록 하자.
알람 함수를 만들어보자.
using Time = std::chrono::steady_clock::time_point;
enum class Sound { Beep, Siren, Whistle };
using Duration = std::chrono::steady_clock::duration;
void setAlarm(Time t, Sound s, Duration d);
위 함수를 이용해서 한 시간 후부터 30초간 소리를 내는 알람 함수를 람다를 이용해서 만들면 다음과 같이 만들 수 있다.
auto setSoundL =
[](Sound s)
{
using namesapce std::chrono;
using namespace std::literals;
setAlarm(steady_clock::now() + 1h,
s,
30s);
};
이 람다를 바인드를 이용해서 만들면 다음과 같이 만들어진다.
using namespace std::chrono;
using namespace std::literals;
using namespace std::placeholders;
auto setSoundB =
std::bind(setAlaram,
steady_clock::now() + 1h,
_1,
30s);
호출 시점의 문제
람다와 똑같이 동작할것이라 생각하겠지만 사실은 문제가 있다. 문제가 있는 코드는 steady_clock::now() 부분이다. 람다의 경우에는 setAlram 이 호출되는 시점의 시간이지만 바인드의 경우에는 바인드 객체가 생성되는 시점의 시간이 되버린다. 위 코드를 람다와 같이 동작되도록 수정하려면 바인드 함수를 한번 더 써야한다.
auto setsoundB =
std::bind(setAlarm,
std::bind(std::plus<>(),
std::bind(steady_clock::now),
1h),
_1,
30s);
오버로딩 문제
만약 setAlarm 함수를 오버로딩하게 되면 문제가 없는 람다와 달리 바인드에서는 컴파일 오류가 발생한다. 타입을 지정해주어 해결할 수 있다.
using SetAlram3ParamType = void(*)(Time t, Sound s, Duration d);
auto setSoundB =
std::bind(static_cast<SetAlram3ParamType>(setAlarm),
std::bind(std::plus<>(),
std::bind(steady_clock::now),
1h),
_1,
30s);
위와 같은 방법으로 컴파일 문제는 해결하였다. 성능의 문제가 있다는 점을 빼면 말이다. 람다의 경우에는 setAlarm 호출을 인라인화 시키는데 반해 바인드의 경우에는 함수 포인터를 사용하기 때문에 인라인화할 가능성이 매우 낮다.
구현과 유지보수의 문제
위에서도 문제를 느꼈겠지만 코드가 복잡해질수록 바인드로의 구현이 어렵다. 어떤 인수가 범위 안에 있는지 확인하는 코드를 작성해보자.
auto betweenL =
[lowVal, highVal]
(const auto& val)
{ return lowVal <= val && val <= highVal; };
auto betweenB =
std::bind(std::logical_and<>(),
std::bind(std::less_equal<>(), lowVal, _1),
std::bind(std::less_equal<>(), _1, highVal));
코드를 이해하는데 람다가 보다 수월함을 느낄 수 있다.
참조와 복사의 문제
Widget 객체를 압축 복사본을 만드는 함수를 통해 어떤 문제가 있는지 알아보자.
enum class CompLevel { Low, Normal, High };
Widget compress(const Widget& w,
CompLevel lev);
Widget w;
auto compressRateB = std::bind(compress, w, _1);
위 함수에서 바인드 객체에 전달되는 w 는 어떻게 전달될까? compressRateB 를 호출하기 전에 w 가 수정된다면 어떻게 되는지 알 수 있을까? 참조로 전달될 때와 값으로 전달될 때의 결과가 달라진다는 점은 숙지해야하는 문제이다.
바인드 객체에 전달하는 방법은 "값"으로 전달된다. 만약 바인드의 작동 방식을 모른다면 추론은 불가능하다. 그러나 람다의 경우에는 의도가 명백하게 드러난다.
auto compressRateL =
[w](CompLevel lev)
{ return compress(w, lev); };
바인드를 사용해야하는 상황
우선 C++14 에서는 바인드를 사용할 필요가 없다. C++11 을 사용하는 사용자에게만 해당하는 내용이다.
이동 갈무리
항목 32에서 이야기 했던 상황으로 C++11 에서는 이동 갈무리를 지원하지 않는다. 자세한 내용은 다음 링크를 통해 살펴보자.
다형적 함수 객체
다음과 같은 상황을 생각해보자.
class PolyWidget {
public:
template<typename T>
void operator()(const T& param) const;
...
};
PolyWidget pw;
// C++11
auto boundPW = std::bind(pw, _1);
boundPW(1930);
boundPW(nullptr);
boundPW("Rosebud");
// C++14
auto boundPW = [pw](const auto& param)
{ pw(param); };
C++14 의 경우에는 auto 를 사용하여 여러가지 형식의 인수들을 사용하는 함수를 하나로 통일시킬 수 있었다. 그에 반해 C++11 에서는 형식을 지정해주어야 하는 문제가 있다. 이 같은 경우에 바인드를 사용하여 하나로 묶을 수 있다.
참고 서적
스콧 마이어스, Effective Modern C++
'C++ > Effective Modern C++' 카테고리의 다른 글
[Effective Modern C++] 항목 36. 비동기성이 필수일 때에는 std::laynch::async를 지정하라 (0) | 2020.02.02 |
---|---|
[Effective Modern C++] 항목 35. 스레드 기반 프로그래밍보다 과제 기반 프로그래밍을 선호하라 (0) | 2020.02.02 |
[Effective Modern C++] 항목 33. std::forward를 통해서 전달할 auto&& 매개변수에는 decltype을 사용하라 (0) | 2020.01.18 |
[Effective Modern C++] 항목 32. 객체를 클로저 안으로 이동하려면 초기화 갈무리를 사용하라 (0) | 2020.01.12 |
[Effective Modern C++] 항목 31. 기본 갈무리 모드를 피하라 (0) | 2020.01.11 |
- Total
- Today
- Yesterday
- C++
- MOVE
- forward
- C
- Unreal
- Modern
- const
- C++14
- C++11
- async
- Effective Modern C++
- std::move
- Overloading
- 다이소
- 포인터
- Future
- Perfect
- std::forward
- Effective
- 람다
- CPP
- Forwarding
- 발아시기
- thread
- auto
- Join
- detach
- Override
- 보편 참조
- 보편참조
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |