목록프로그래밍/EMC++ (31)
INDIES
std::bind보단 람다C++11에서 거의 대부분의 경우 std::bind보다는 람다 표현식을 쓰는 것이 더 좋은 선택이다. 람다의 기능이 대폭강화된 C++14 역시 마찬가지고. 그 이유에는 여러 가지가 있지만, 우선 가장 첫번째는 가독성이다. std::bind를 이용해 작성된 코드보다 람다 표현식으로 작성된 코드의 가독성이 더 뛰어나다.using Time = std::chrono::steady_clock::time_point; enum class Sount { Beep, Siren, Whistle }; using Duration = std::chrono::steady_clock::duration; //타임 t가 되면 d 시간동안 s 소리를 냄 void SetAlarm(Time t, Sound s, D..
auto&&를 std::forward할 땐 decltypeC++ 14에 추가된 generic lambda라는 기능은 람다의 매개변수 정의에 auto를 쓸 수 있게 해주는 굉장히 흥미로운 기능이다. 이 기능의 구현은 람다의 클로져 클래스 내부 멤버 함수 operator()를 템플릿으로 만드는 것으로 이루어진다.auto f = [](auto x){ return func(normalize(x)); }; //내부 클로져 클래스의 구현 class Closure { public: template auto operator()(T x) const { return func(normalize(x)); } }위 예제에서 람다는 단순히 인자 x를 normalize로 전달해주는 역할만을 한다. 이 때 normalize 함수의 ..
move에는 init capture가끔 by-value 캡쳐도, by-reference 캡쳐도 마음에 안 들 때가 있다. move-only 객체(std::unique_ptr이나 std::future 등)를 클로져 내부로 이동시키고 싶은 경우가 바로 그런 경우인데, 문제는 C++11은 이런 경우에 대한 방법을 지원하지 않는다. 클로져 내부로 복사는 비싸지만 이동은 싼(vector 같은 컨테이너) 객체를 이동시키고 싶은 경우에도 마찬가지로 C++11에서는 언어적 차원의 기능 지원이 없다. 하지만 C++14는 다르다. C++14에서는 이런 경우에 클로져 내부로 객체를 이동시키는 방법을 제공한다.init captureC++14에 추가된 init capture 기능을 이용하면 외부의 객체를 클로져 내부로 이동시킬..
Lambda Expression람다는 함수 객체를 만드는데 굉장히 유용한 방법이다. 람다에 관련된 용어들은 서로 헷갈리는 경우가 많으니 간단히 한 번 짚고 넘어가자.람다 표현식(lambda expression)은 단순한 표현식이다. 이는 소스코드의 일부이다.std::find_if(container.begin(), container.end(), [](int val){ return 0 < val && val < 10; }); 이 코드에서 [](...){ ~ } 부분이 람다 표현식이다.클로져(closure)는 람다에 의해 런타임에 생성되는 객체이다. 캡쳐 모드에 따라 클로져는 캡쳐된 데이터의 복사본 또는 레퍼런스를 갖고 있게 된다. 위 코드의 std::find_if의 호출에서 런타임에 세 번째 인자(람다 표현..
perfect forwarding 실패 상황에 익숙해져라prefect forwarding을 쓰면 하나의 함수에서 다른 함수로 인자들을 완벽하게, 즉 인자 그 자체 뿐만 아니라 인자들의 미묘한 특성들(lvalue니 rvalue니, cv지정자니 하는 것들)까지 전달해준다.//원소 하나 전달 template void fwd(T&& param) { f(std::forward(param)); } //임의 개수 원소 전달 template void fwd(Ts&&... params) { f(std::forward(params)...); }그러나, 문제는 perfect forwarding이 불가능한 상황이 있다는 것이다. 그냥 f함수를 호출할 때는 잘 되는데 fwd 함수를 거쳐서 호출하는 건 불가능한 경우가 존재하기 ..
move가 별로 좋지 않다고 가정해라C++11에서 추가된 move semantics는 그 설명만 들으면 굉장히 좋아보인다. move semantics를 이용하면 컨테이너 이동을 포인터 복사하는데 걸리는 시간만에 처리하고, 임시 객체의 복사를 굉장히 빠르게 수행할 수 있다. 하지만, 실제로는 이런 표현들은 굉장히 과장된 것이다. move 연산은 존재하지도, 비용이 싸지도, 사용되지도 않는다고 가정하라.일단 move semantics를 지원하지 않는 타입에 대해서 먼저 살펴보자. 우선 C++ 98 시절에 작성한 타입들의 경우, C++11 기반의 컴파일러로 바꾸고 move를 쓰고 한다 해도 별로 성능 상의 이득을 못 볼 가능성이 있다. item 17에서 말했듯이 default move 생성자, 이동 대입 연산..
reference collapsing 이해universal reference에서 어떻게 타입 T로부터 넘어온 인자가 lvalue인지 rvalue인지 알 수 있을까? 이제 드디어 그에 관한 이야기를 할 차례다.template void func(T&& param);원리는 간단하다. 만약 인자로 lvalue가 넘어온다면 T는 lvalue reference로 추론되고, rvalue가 넘어온다면 T는 레퍼런스가 아닌 타입(값 타입)으로 추론된다. 따라서,Widget widgetFactory(); Widget w; func(w); //T는 Widget& func(widgetFactory()); // T는 Widget그런데 뭔가 이상하다. 여기서 T가 각각 Widget&, Widget으로 추론된다고 했는데, 그럼 그..
universal reference 오버로딩 회피법오버로딩 회피말 그대로 오버로딩 자체를 안 해버리는 것이다. item 26을 기준으로 하자면, logAndAdd 함수에서 인자로 universal reference를 받는 경우는 함수 이름을 logAndAddName으로, index를 인자로 받는 경우는 함수 이름을 logAndAddNameIdx로 짓는 것이다. 이렇게 하면 애초에 오버로딩 자체가 안 일어나므로 아무런 문제 없이 잘 해결된다. 단, 이 해결법은 item 26에서도 언급된 클래스 생성자 관련 문제에는 사용할 수 없다는 단점이 있다.Pass By const T&또 다른 해결책은 universal reference를 쓰지 않고, C++ 98때처럼 const T&타입을 넘기는 것이다. 이렇게 하면..
Universal Reference는 오버로딩을 피해라이름을 인자로 받아서, 로그를 남기고 해당 이름을 어떤 전역 자료구조에 저장하는 함수를 생각해보자.std::multiset names; void logAndAdd(const std::string& name) { auto now = std::chrono::system_clock::now(); log(now, "logAndAdd"); names.emplace(name); }위 코드는 동작은 정상적으로 하지만, 별로 효율적인 코드는 아니다. 이 함수를 호출할 때 있을 수 있는 3가지 경우를 생각해보자.std::string petName("Darla"); //case 1 : lvalue std::string 넘기는 경우 logAndAdd(petName); /..
std::move, std::forward의 바른 사용적합한 위치에 쓰기class Widget { public: Widget(Widget&& rhs) : name(std::move(rhs.name)), p(std::move(rhs.p)) { ... } ... private: std::string name; std::shared_ptr p; };rvalue reference는 이동(move)될 수 있는 객체를 말한다. 반드시 이동되는 것은 아니지만 어쨌든 이동될 가능성은 있다는 뜻이다. 그리고 이렇게 rvalue reference로 인자를 넘겨서 move를 통해 멤버들을 초기화하는 경우는 그 목적에 따라 std::move를 쓰는 게 적합할 것이다.반면에 universal reference를 쓰는 경우엔, ..