목록분류 전체보기 (68)
INDIES
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으로 추론된다고 했는데, 그럼 그..
SFINAE템플릿 메타 프로그래밍의 기본이 되는 기법. Substitution Failure Is Not An Error의 줄임말이다. 다짜고짜 SFINAE부터 다루면 너무 어려우니 왜 이 개념이 필요하고 어떤 때에 쓰이는지 차근차근 살펴보자. SFINAE는 함수 템플릿과 관련하여 나타나는 특성이기 때문에 함수 템플릿의 컴파일 과정에 대해 기본적인 개념이 있어야 이해하기가 쉽다.함수 템플릿의 컴파일 과정과 관련된 아래 주제들은 다 하나하나 상세히 파고 들어가면 굉장히 복잡하고 알아야 할 내용이 많지만, SFINAE가 주제지 함수 템플릿이 주제가 아니므로 함수 템플릿과 관련한 내용은 기초적인 개념만 간단히 서술했다. 혹 함수 템플릿의 컴파일 과정에 대한 상세한 내용이 궁금하신 분들은 따로 더 찾아보시는 것..
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를 쓰는 경우엔, ..
universal reference, rvalue reference어떤 타입 T에 대한 rvalue reference는 T&&로 나타낸다. 따라서 소스 코드에 나타나는 T&&들은 모두 T 타입에 대한 rvalue reference로 봐도 될 것이다. 진짜 그럴까? 안타깝게도 실제로는 전혀 그렇지 않다.//rvalue reference void f(Widget&& param); //rvalue reference Widget&& var1 = Widget(); //not rvalue reference auto&& var2 = var1; //rvalue reference template void f (std::vector&& param); //not rvalue reference template void f(T..