[Effective Modern C++] Chapter 8. 다듬기 [항목 41~42]

Chapter 8. 다듬기 항목 41. 이동이 저렴하고 항상 복사되는 복사 가능 매개변수에 대해서는 값 전달을 고려하라. 왼값 참조와 오른값 참조 버전 2개를 모두 만드는 것은 코드 중복, 유지보수 측면에서 단점이고, 보편 참조 전달 버전은 항목 26/27/30 에서 말했던 것들과 같은 문제들이 발생할 수 있다. 따라서 약간의 효율성을 포기하면 이러한 단점들을 피할 수 있는데, 그것이 바로 값 전달을 활용하는 것이다. (효율성 : 보편 참조 버전 >= 왼값 참조 버전 + 오른값 참조 버전 >= 값 전달 버전) 그러나, 값 전달의 경우에도 주의해야 할 점들이 있다. 1. 잘림 문제 (slicing problem) 2. 값 전달 함수들이 꼬리를 물고 호출되면, 전체적인 성능이 급격히 하락할 수 있다. 3. "값 전달 (복사 생성) -> 이동 배정" 의 경우 "참조 전달 -> 복사 배정" 보다 훨씬 비쌀 가능성이 있다. (예를 들면, std::string이나 std::vector등의 memory allocation 때문에) 흠, 나는 값 전달 버전이 얼마나 유용할 지 의문이 든다. 일단 이동 연산 하나가 불필요하게 낭비된다. 사실 이동이 저렴한 경우에는 유지보수, 코드중복 해결의 장점을 봤을 때 이러한 사소한 낭비를 무시할 수 있다. 그러나 사실 큰 문제는 이동이 저렴하다고 생각했는데 저렴하지 않을 수 도 있다는 것이다. std::string이나 std::vector같은 경우, memory allocation 때문에 값 전달 후 이동 배정을 하는 것이 상당히 느려질 수 있는데, 코드를 짤 때 이러한 점을 간과하거나 실수 할 가능성이 크다. 또한, 잘림 문제도 주의해야 한다. 즉, 값 전달을 사용하는 것은 참조 버전보다 실수할 가능성이 더 크기 때문에 일단 기본적으로는 피해야 한다고 생각한다. 내 생각에는 일단 우선적으로 왼값/오른값 참조 버전을 사용하다가, 성능을 더 강화할 필요가 있을 경우...

[Effective Modern C++] Chapter 7. 동시성 API [항목 35~40]

Chapter 7. 동시성 API 항목 35. 스레드 기반 프로그래밍보다 과제 기반 프로그래밍을 선호하라. 확실히 과제 기반 프로그래밍은 스레드 기반 프로그래밍보다 더 쉽고 가독성이나 설계 측면에서 더 우월하다. 하지만, 과하게 마구잡이로 사용하는 것을 주의해야 한다고 느꼈다. 스레드를 쓰면 간단하게 되는 것을 과제 기반으로 하려고 억지로 짜 맞추는 것은 좋지 않다. 그리고 과제 기반 프로그래밍은 스레드 기반 보다 더 높은 추상화로서, 내부적으로 스레드 고갈, over subscription, load balancing등의 문제를 해결해줄 수 도 있다. 실제로 현재에는 std::async가 visual studio에서는 PPL을 바탕으로 위와 같은 것을 어느 정도 해결해주고 있다. 그러나, libc++ (llvm의 std 구현)과 libstdc++ (gcc의 std구현)의 경우 비효율적인 방법으로 구현되어 있다. (관련 내용 : http://rpgmakerxp.tistory.com/63 ) 정말 비동기 작업을 효율적으로 진행해야 하고, 더 많은 부분들(thread priority, affinity, thread pool등)을 직접 컨트롤 해야하는 경우에는 std::async가 적합하지 않다. 이런 경우는 std::thread 혹은 Windows API / pthreads 등을 사용해야 한다. 그리고, 아직 C++11/14의 과제 기반 프로그래밍은 빈약한 편이므로, 좀 더 본격적인 과제 기반 프로그래밍이 필요한 경우에는 TBB, PPL, HPX등을 사용하는 것이 좋아 보인다. (그러나, C++의 향후 표준들에서 점차 강력해질 것이다.) [기억해 둘 사항들]  - std::thread API에서는 비동기적으로 실행된 함수의 반환값을 직접 얻을 수 없으며, 만일 그런 함수가 예외를 던지면 프로그램이 종료된다.  - 스레드 기반 프로그래밍에서는 스레드 고갈, 과다구독, 부하 균형화, 새 플랫폼으로의 적응을 독자가 직접 처리해야 한다...

[Effective Modern C++] Chapter 6. 람다 표현식 [항목 31~34]

Chapter 6. 람다 표현식 항목 31. 기본 갈무리 모드를 피하라. 참고로, 갈무리는 'capture'를 의미한다. (첨엔 어색했으나 나도 이제 완전히 이 번역에 익숙해져버렸다.) 기본 갈무리 모드 (default capture mode)는 '참조 갈무리 모드'와 '값 갈무리 모드'가 있다. 기본 갈무리 모드를 피해야 하는 이유는 크게 dangling reference 위험과 명시적이지 않다는 점(그래서 착각/실수 할 수 있다는 점) 때문이다. 일단, 참조 갈무리 모드에서 dangling reference 문제가 발생할 수 있다는 것은 너무 당연하고, 값 갈무리 모드에서도 pointer가 capture되는 경우에 그러한 문제가 발생 할 수 있다는 것은 당연하다. 사실 결정적인 문제는 명시적이지 않다는 점이다. 이로 인해 문제가 발생 할 수 있을 만한 상황을 정리하면 다음과 같다. 1. this pointer의 캡처     - 숨겨져있던 this pointer가 캡처됨으로 인해 dangling pointer 등의 문제가 발생할 수 있다. 2. 람다표현식을 복붙(Copy&Paste) 할 경우, 실수할 가능성이 커진다.     - 유용한 람다표현식을 그대로 다른 곳에서 쓸 경우가 있을 수 있다. 이 경우, capture list가 명시적이지 않아서 문제들이 발생할 소지가 있다. 3. 전역 변수나 static 같이 global scope를 가진 변수들 (즉, non-automatic variable들)의 값이 복사로서 capture될 것이라는 착각을 할 수 있다.     - 오직 automatic variable들(즉, 지역변수들) 만 capture될 수 있는데, 기본 값 갈무리 모드를 사용하면, non-automatic variable들도 값 복사로서 capture될 것이라는 착각을 할 여지가 있다. 즉, 기본 갈무리 모드의 문제는 결국 '명시적이지 않다'는 것이다. 필...

PyPy RPython Translation Toolchain

이미지
그 동안 PyPy를 단순히 JIT을 지원하는 빠른 python implementation 이라고만 알고 있었다. 그런데 알고 보니 단순한 python implementation 이상의 의미를 갖는 상당히 멋있는 프로젝트였다. PyPy는 2가지 파트로 나뉜다. 1. RPython 문법으로 작성된 Python Interpreter 2. RPython Translation Toolchain 1번의 python interpreter를 2번의 toolchain에 넣으면, JIT, garbage collector 등의 기능이 첨가된 C언어로 된 interpreter가 결과로서 나온다. ???!!  매우 놀라울 것이다. 2번의 toolchain은 Python 소스 코드나 syntax trees 에 대한 정보는 전혀 가지고 있지 않는다. 그런데 JIT과 garbage collector를 비롯한 기능들을 첨가해준다니?! 그래서 내가 멋있다고 한 것이다... (출처 : https://www.toptal.com/python/why-are-there-so-many-pythons) PyPy의 구조를 그림으로 보자면 위와 같다. (PyPy Interpreter가 1번, PyPy compiler가 2번에 해당한다.) (출처 : http://rpython.readthedocs.io/en/latest/translation.html) RPython translation toolchain의 실제 플로우를 그림으로 보자면 위와 같다. 기본적으로 RPython으로 작성된 interpreter 코드가 필요하고 추가적으로 low level hint들을 통해 세부적인 과정에 참여할 수 있다. 그리고 translation toolchain의 backend는 현재 C언어만 가능하지만, 설계 상 더 추가가 가능한 구조이다. 그리고 이러한 RPython Translation Toolchain을 이용하면 Python 뿐만 아니라 모든 언어들의 'JIT'ed Inter...

[Effective Modern C++] Chapter 5. 오른값 참조, 이동 의미론, 완벽 전달 [항목 23~30]

Chapter 5. 오른값 참조, 이동 의미론, 완벽 전달 항목 23. std::move와 std::forward를 숙지하라. #include <iostream> class Example { public: Example() = default; Example(const Example &) { std::cout << "copy constructor" << std::endl; } Example(Example &&) { std::cout << "move constructor" << std::endl; } Example(const Example &&) { std::cout << "const move constructor" << std::endl; } }; /* std::forward is meaningful only for universal reference in template. */ void test1(Example ex) { std::cout << "Test 1" << std::endl; Example a(std::forward<Example>(ex)); // move constructor Example b(std::forward<Example&>(ex)); // copy constructor Example c(std::forward<Example&&>(ex)); // move constructor } void test2(Example& ex) { std::cout << "Test 2" << std::endl; Example a(std::forward<Exam...

[Effective Modern C++] Chapter 4. 똑똑한 포인터 (Smart Pointer) [항목 18~22]

Chapter 4. 똑똑한 포인터 (Smart Pointer) 항목 18. 소유권 독점 자원의 관리에는 std::unique_ptr를 사용하라. 보통 smart pointer를 처음 접한 사람들은 std::shared_ptr만을 남용(?)하는 경향이 있다. 그러나 std::shared_ptr이 매우 강력한 존재이긴 하지만 크게 2가지 측면에서 단점이 있다. 첫 번째는 overhead이다. 참조 계수를 관리하기 위해 어쩔 수 없이 overhead가 존재한다. 두 번째는 돌이킬 수 없다는 점이다. 한번 std::shared_ptr에 pointer를 물리고 나면 다시는 일반 pointer로 복귀할 수 없다. (단순히 .get()을 쓰면 raw pointer를 받을 수 있을 뿐이다. 여전히 프로그램 어딘가에서 포인터를 참조할 수 있을 수 있다.) 이러한 단점들 때문에 나는 기본적으로 std::unique_ptr을 사용한다. std::unique_ptr은 덜 강력하지만 위 2가지 단점이 없다. 추가적인 메모리를 요구하는 커스텀 삭제자를 지정하지 않는다면 performance는 raw pointer와 사실상 동일하고, std::unique_ptr를 사용하다가 적합하지 않은 상황이 오면 언제든지 정책을 raw pointer나 std::shared_ptr등으로 바꿀 수 있다. 참고) raw pointer, std::unique_ptr, std::shared_ptr의 '생성 및 삭제' 성능 비교 : http://www.modernescpp.com/index.php/memory-and-performance-overhead-of-smart-pointer 특히 어떻게 쓰일 지 모르는 포인터를 반환해야 하는 경우는 std::unique_ptr를 쓰는 것이 좋다. std::unique_ptr은 어떤 형태로든지 변환이 가능하기 때문이다. 특별한 목적이 있는 경우가 아니라면, 팩토리 함수등에서 std::shared_ptr 을 반환하는 것은 좋지 않은 습관이다. ...

[Effective Modern C++] Chapter 3. 현대적 C++에 적응하기 [항목 7~17]

Chapter 3. 현대적 C++에 적응하기 항목 7. 객체 생성 시 괄호(())와 중괄호({})를 구분하라. 내 경험과 개인적인 의견에 따르면,,, 객체 생성 시 왠만하면 괄호를 사용하고, 클래스 내에서 멤버 변수의 기본 값을 설정할 때와 std::initializer_list 를 매개변수로 받는 생성자를 호출 할 때에만 중괄호 초기치를 사용하는 것이 좋다. 이러면 별 문제가 없다. 중괄호 초기치의 장점은 아래와 같다. 1. 가장 광범위하게 적용할 수 있는 초기화 구문이다. 2. 좁히기 변환을 방지할 수 있다. 3. C++의 most vexing parse에서 부터 자유롭다. ("선언으로 해석 할 수 있는 것은 항상 선언으로 해석해야 한다.") 일단 1번 경우와 2번의 장점으로 인해 클래스 내에서 멤버 변수의 기본 값을 설정할 때에는 중괄호 초기치를 사용하는 것이 좋다. 그리고 2번 장점의 경우, 대부분 auto의 올바른 사용으로 인해 장점이 무색해진다. 어차피 auto 를 사용하면 암시적인 좁히기 변환이 불가능하기 때문이다. 오히려 auto와 중괄호 초기치의 결합은 type deduction에 있어서 혼란을 가져올 수 있기 때문에 좋지 않다. 따라서 chapter 2의 교훈에 따라 auto를 적극적으로 그리고 잘 활용한다면, 일반적으로 중괄호 초기치보다 괄호를 쓰는 것이 훨씬 좋다. 그러면 3번의 장점을 봐보자. 3번 부분은 ReturnValue obj(); 같은 구문의 경우 이 것이 함수 선언으로 해석되는 것인데, 중괄호 초기치를 사용하면 그럴 일이 없기 때문에 좋다는 것이다... 글쌔... 그냥 ReturnValue obj;와 같이 사용하는 게 낫지 않을 까 싶다. 이제 한번 템플릿 안에서 객체를 생성할 때는 괄호와 중괄호 중 어떤 걸 사용할 지 생각해보자. 내 생각에는 템플릿에서는 더더욱 괄호를 쓰는 게 옳다. 템플릿 매개변수로 어떤 타입이 올지 모르는데 만약 그 타입의 생성자 중에 매개변수로 std::init...