컴퓨터과학 썸네일형 리스트형 C++ (24-1) 보편 참조와 오른값 참조를 구별할 것 "진리가 너희를 자유롭게 하리라"라는 말이 있긴 하지만, 상황에 따라서는 잘 선택된 거짓말 역시 진리만큼이나 우리를 자유롭게 합니다. 이번 항목이 그런 거짓말에 해당합니다. 그런데 우리가 다루는 것은 소프트웨어이므로, "거짓말"이라는 단어는 피하기로 합시다. 대신, 이번 항목이 하나의 "추상(abstraction)"을 함의한다고 말하는 것이 좋겠습니다. 어떤 형식 T에 대한 오른 값 참조를 선언할 때에는 T&&라는 표기를 사용합니다. 그래서 소스 코드에 "T&&"를 발견했다면 그것이 오른 값 참조라고 가정하는 것은 당연해 보입니다. 그러나 안타깝게도 그렇게 단순하지는 않습니다. void f(Widget&& param); // 오른값 참조 Widget&& var1 = Widget(); // 오른값 참조 a.. 더보기 C++ (23-2) std::move와 std::forward를 숙지할 것 결과적으로 그 멤버 초기화 목록은 std::string의 복사 생성자를 호출합니다. text를 오른 값으로 캐스팅했어도 그런 일이 벌어집니다! 이런 행동 방식은 const 정확성을 유지하기 위해 꼭 필요한 것입니다. 일반적으로 한 객체의 어떤 값을 바깥으로 이동하면 그 객체는 수정되며, 따라서 전달된 객체를 수정할 수도 있는 함수(이동 생성자가 그런 함수에 속한다)에 const 객체를 전달하는 일을 C++ 언어가 방지하는 것은 당연한 일입니다. 이 예에서 배울 점이 두 가지 있습니다. 첫째로, 이동을 지원할 객체는 const로 선언하지 말아야 합니다. const 객체에 대한 이동 요청은 소리 없이 복사 연산으로 변환됩니다. 둘째로, std::move는 아무것도 실제로 이동하지 않을 뿐만 아니라, 캐스팅되.. 더보기 C++ (23-1) std::move와 std::forward를 숙지할 것 std::move와 std::forward를 이해하는 데에는 이들이 하지 않는 것의 관점에서 접근하는 것이 유용합니다. std::move가 모든 것을 이동하지는 않습니다. std::forward가 모든 것을 전달하지는 않습니다. 실행 시점에서는 둘 다 아무것도 하지 않습니다. 이들은 실행 가능 코드를 전혀, 단 한 바이트도 산출하지 않습니다. std::move와 std::forward는 그냥 캐스팅을 수행하는 함수(구체적으로는 함수 템플릿)입니다. std::move는 주어진 인수를 무조건 오른값으로 캐스팅하고, std::forward는 특정 조건이 만족될 때에만 그런 캐스팅을 수행합니다. 이것이 전부입니다. 물론 이 답에서 또 다른 질문들이 비롯되긴 하지만, 기본적으로는 이것이 전부입니다. 좀 더 구체적.. 더보기 C++ (22-4) Pimpl 관용구를 사용할 떄에는 특수 멤버 함수들을 구현 파일에서 정의할 것 이 구현들은 복사 생성자의 매개변수 rhs(복사 배정 연산자의 경우에는 *this)가 이미 이동되어서 해당 pImpl 포인터가 널인 경우를 반드시 처리해 주어야 한다는 점만 빼면 간단합니다. 전체적으로 이 구현들은 컴파일러가 Impl에 대해 복사 연산들을 작성해 준다는 사실과 그 연산들이 각 필드를 자동으로 복사할 것이라는 사실을 활용합니다. 즉, Widget의 복사 연산들은 컴파일러가 작성한 Widget::Impl의 복사 연산들을 호출합니다. 두 함수 모두, new를 직접 사용하는 것보다 std::make_unique를 선호하라는 Chapter (21)의 조언을 따랐음을 주목하기 바랍니다. 객체 내부의(지금 예에서는 Widget 안의) pImpl 포인터가 해당 구현 객체(지금 예에서는 Widget::I.. 더보기 C++ (22-3) Pimpl 관용구를 사용할 떄에는 특수 멤버 함수들을 구현 파일에서 정의할 것 컴파일러는 형식의 정의를 보게 되면 그 형식을 완전한 형식으로 간주합니다. 그리고 Widget::Impl의 정의는 widget.cpp에 있습니다. 따라서, Widget::Impl의 정의 이후에 컴파일러가 그 소스 파일에만 있는 Widget의 소멸자의 본문(즉, 컴파일러가 std::unique_ptr 자료 멤버를 파괴하는 코드를 작성하는 곳)을 보게 한다면 클라이언트 코드가 문제없이 컴파일됩니다. 그런 식으로 코드를 배치하는 것은 간단합니다. widget.h에서 Widget의 소멸자를 선언하되 정의는 하지 않습니다. class Widget { // 이전처럼 "widget.h" 안에서 public: Widget(); ~Widget(); // 선언만 해둔다 ... private: // 이전과 동일 struct.. 더보기 C++ (22-2) Pimpl 관용구를 사용할 떄에는 특수 멤버 함수들을 구현 파일에서 정의할 것 이제는 Widget이 std::string이나 std::vector, Gadget 형식을 언급하지 않으므로, Widget의 클라이언트는 그 형식들의 헤더들을 #include로 포함시킬 필요가 없습니다. 이 덕분에 컴파일이 빨라지며, 또한 그 헤더들에서 뭔가가 바뀌어도 Widget 클라이언트에는 영향이 미치지 않습니다. 선언만 하고 정의는 하지 않은 형식을 불완전 형식(incomplete type)이라고 부르기도 합니다. Widget::Impl이 그런 형식입니다. 불완전 형식을 가리키는 포인터를 선언하는 것은 불완전 형식으로 할 수 있는 몇 안 되는 일 중 하나이고, Pimpl 관용구는 바로 그러한 능력을 활용합니다. 이처럼 불완전 형식을 가리키는 포인터를 하나의 자료 멤버로 선언하는 것이 Pimpl 관용.. 더보기 C++ (21-3) new를 직접 사용하는 것보다 std::make_unique와 std::make_shared를 선호할 것 / (22-1) Pimpl 관용구를 사용할 떄에는 특수 멤버 함수들을 구현 파일에서 정의할 것 [new를 직접 사용하는 것보다 std::make_unique와 std::make_shared를 선호할 것] 한 예로, 이전에 본 예외에 안전하지 않은 processWidget 함수 호출 예제를 조금 고쳐서 살펴보겠습니다. 이번에는 커스텀 삭제자를 사용합니다. void processWidget(std::shared_ptr spw, int priority); // 이전과 동일 void cusDel(Widget *ptr); // 커스텀 삭제자 다음은 예외에 안전하지 않은 호출입니다. processWidget(std::shared_ptr(new Widget, cusDel), computePriority()); // 이전처럼 자원 누수 위험이 있음! 이전에도 설명했듯이, 만일 computePriority가 "n.. 더보기 C++ (21-2) new를 직접 사용하는 것보다 std::make_unique와 std::make_shared를 선호할 것 std::make_shared를 사용하면 이런 문제가 생기지 않습니다. 이 경우 호출 코드는 다음과 같은 모습입니다. processWidget(std::make_shared(), computePriority()); // 자원 누수의 위험이 없음 실행시점에서 std::make_shared가 먼저 호출될 수도 있고 computerPriority가 먼저일 수도 있습니다. 만일 std::make_shared가 먼저라면, 동적으로 할당된 Widget을 가리키는 생 포인터는 computerPriority가 호출되기 전에 반환된 std::shared_ptr 안에 안전하게 저장됩니다. 그런 다음 computerPriority가 예외를 방출한다면, std::shared_ptr의 소멸자가 피지칭 Widget 객체를 파괴합.. 더보기 이전 1 2 3 4 ··· 8 다음