std::unique_ptr을 사용하자

07/03/2018 c++, programming No comments

최신의 c++에서 동적메모리 할당에 raw pointer를 사용하는 것보다는 std::unique_ptr을 사용해야 할 이유는 충분히 많이 있다.

std::unique_ptr의 사용해야 할 이유가 생소한 사람은 “스콧 마이어스의 Effective Modern C++” 부터 읽어보길 추천한다.

여기서는 생각해볼 문제로 2가지 관점에서 std::unique_ptr을 써야 하는 이유를 이야기해보고자 한다.

 

1. 예외로 부터 보호하기

c++에서는 RAII를 활용하는 것이 정말 좋다. 즉, raw pointer보다 std::unique_ptr이 더 좋다.

만약 CQueue::Add 함수가 예외를 던진다면 어떻게 될까?

UseRawPointer(); 함수는 메모리릭이 발생하지만, UseUniquePointer(); 함수는 메모리릭이 발생하지 않는다.

 

2. 잘못 쓰기 어렵게 만들기

예를 들어서 CStream이라는 클래스가 있고, 생성자에서 CBuffer*를 입력해줄 것을 요구한다고 하자.

CBuffer* 를 할당을 해서 넣어주어야 하는 것일까? 그렇다면 해제는 CStream이 하는것인가? 외부에서 따로 해야 하는 것인가?

new를 안해도 되는 것일까?

 

실제로 개발을 하다 보면, 반드시 new를 요구하는 케이스가 있고, 그렇지 않은 케이스가 있다.

개발하면서는 기존에 사용한 코드를 참조하거나, 내부의 구현을 보고 적당히 잘 쓰는게 현실이다.

나같은 경우는 c++에 move 연산자가 추가되면서 부터는 new를 요구하는 곳에서는 std::unique_ptr을 사용하려고 하고 있다.

(인터페이스의 일관성 문제 등도 있고, 협업의 관점도 있고, 이미 진행된 코드에서는 그렇게 하지 못하지만…)

 

Spin-Loop

05/31/2018 c++, programming No comments

멀티쓰레딩 프로그래밍에서 락을 잡는 타이밍이 짧다면 이론적으로 spin lock을 사용하여 busy waiting을 하는 것이 성능에 좋다고 알고 있다.

하지만, 실제로 spin lock으로 효과를 보기 쉽지 않은데, CPU에 이를 위해 pause라는 명령어가 존재한다.

spin lock이라는게 결국 변수의 값이 원하는 값이 될때까지 계속 loop를 돌리는 것인데, pause라는 명령어를 통해 CPU에게 지금 spin loop를 돌고 있다는 힌트를 주고, CPU가 메모리버스 속도 등을 감안해서 변수가 바뀌는데 필요한 최소한의 시간만큼 다음 명령어를 실행하지 않는 것이다.

– 참고 https://software.intel.com/sites/default/files/m/d/4/1/d/8/17689_w_spinlock.pdf (w_spinlock.pdf)

이를 테스트 해보았다.

인텔계열의 CPU에서는 _mm_pause();라는 함수로 pause를 사용 할 수 있다.

  • Visual Studio에서는 YieldProcessor(); 라는 매크로 함수도 동일하다.

간단하게 코드를 보면

실제로 pause 명령어를 사용했을 때와 안했을 때를 테스트 해봤을 때….

어마어마한 차이가 났다.

 

테스트 케이스는 I7-6700K CPU (8코어) 에서 8개의 쓰레드에서 스핀락을 걸고, 스핀락이 성공하면 Sleep(0)로 잠깐 휴지기를 주었으며, 시간 측정은 spin-loop 구간만 측정하였다.

결과는…

실행할때마다 결과가 다르긴 한데… 10배이상의 차이가….

git p4 사용하기 – 인코딩문제 해결하기

05/07/2018 게임개발 1 comment

최근에 합류한 게임프로젝트는 VCS로 Perforce를 사용하고 있다.

개인적으로는 Perforce를 처음사용해보는데, 클라이언트의 UI구성등이 익숙하지 않을뿐, Subversion과 크게 다르지 않아서 금방 적응 할 수 있었다.

적응을 하면 할 수록 git이 계속 아쉬웠고, Perforce에 git을 연동해서 사용 할 수 있는 방법이 있다는 것은 알고 있었기 때문에 사용해보기로 했다.

Perforce에서 git을 사용 할 수 있는 방법은 2가지가 있는데…

  1. Git Fusion
  2. git-p4

Git Fusion은 서버를 건드려야 하니, 클라이언트만으로 해결 할 수 있는 p4를 사용해 보았다.

정말 잘 된다!!

git은 굉장히 유연하게 잘 설계되어 있는 것 같다.

Perforce 뿐만이 아니라, Subversion과 Mercurial도 사용 하는 방법이 있다.

https://git-scm.com/book/en/v2/Git-and-Other-Systems-Git-as-a-Client

 

윈도우에서 git-p4를 사용할 수 있는 기본 셋팅은 이곳을 참조하자.

http://ericlathrop.com/2012/12/how-to-set-up-git-p4-in-windows/

 

하지만! 인코딩과 관련된 문제를 겪게 되었는데….

내가 사용하고 있는 Perforce는 cp949 한글 인코딩을 사용하고 있다.

git의 기본 인코딩은 utf-8이지만, 이것을 바꿀 수 있는 방법이 있다.

git config –global i18n.commitEncoding cp949

git config –global i18n.logOutputEncoding cp949

그리고 git의 기본 gui client에서 잘 보이게 하려면 이렇게 하면 된다.

git config –global gui.encoding cp949

이렇게 세팅해서 사용하면, git bash와 git gui, gitk만 사용한다면 아무 문제없이 잘 사용 할 수 있다.

하지만… 저것들로만 불편해서 보통 별도의 GUI Client를 사용하는데…

이 GUI Client중에 인코딩을 해결 할 수 있는 것을 못찾았다….

아…. 도저히 못 찾겠다. 혹시 방법을 아는 분은 알려주길….

 

그렇다고 이대로, 기본툴로만 개발 할 수는 없는일…

용감하게(?) git-p4.py를 수정해보기로 했고, 의외로 쉽게 해결 할 수 있었다.

원리는 git p4 sync로 가지고 올때는 cp949를 utf-8로 변경, git p4 submit을 할 때는 utf-8을 cp949로 변경

이렇게 하기 위해서 git config에 perforce의 인코딩이 무엇인지 알려주어야 한다.

git config –global git-p4.charset cp949

(위에 하던 설정들은 하지말고, 이것만!!)

https://github.com/yonmy/git/blob/git-p4-charset/git-p4.py

공식 git에 이 수정사항을 pull request한 상태인데,

반영해 줄라나? 이슈로 만든다음에 요청해야 하는 것인가?

영어가 약해서… 모르겠다… 안해주면 말고…

게임개발에서 버젼 관리 시스템

05/07/2018 게임개발 No comments

게임회사에서 사용하는 버젼 관리 시스템은 주로 다음중 하나를 사용한다.

  1. Subversion
  2. Perforce
  3. Git

일반적으로는 (아직까지는) 무료로는 Subversion을, 유료로 더 빠르고 안정적인 것을 사용하고자 하면 Perforce를 사용하고 있다.

전세계적으로 오픈소스 프로젝트들의 저장소로 GitHub의 인기가 많아지면서, git을 사용하는 프로젝트들이 많은데, 아직까지 (적어도 국내의) 게임회사에서 게임개발용으로는 많이 활성화가 되지 못하고 있는 것 같다.

게임 프로젝트에서 git의 사용이 저조한 이유는 게임의 경우 binary파일의 양이 text보다 많은데,  binary파일의 버젼관리에서 git의 장점을 살릴 수 없고, 단점만 생기기 때문인 것 같다.

사실, 프로그래머로써 많이 사용 해보면 누구나 느끼겠지만, git의 훌륭함에 빠져들 수 밖에 없다.

내가 6년넘게 git을 사용하는 회사에 재직할때의 경험은…

첫번째 프로젝트는 git(소스코드) + Subversion(나머지)을 사용하고 있었고,

두번째 프로젝트는 초기 환경 구성에 관여하였는데, git(소스코드) + Subversion(나머지)을 선택하였다.

세번째 프로젝트에서도 초기 환경 구성을 담당하였는데, 2개의 저장소로 운영한다는 것이 단점도 꽤 많기때문에(버젼일치시키기 곤란 등…) git으로 통일하자라는 프로그래머들의 의견이 있었고, 이 부분에 대해서 고민을 할 수 밖에 없었다.

대부분의 git에 익숙해진 프로그래머들은 좋은점을 잘 알기때문에 개발자(디자이너, 아티스트)는 당연히 공부해서 사용하는게 맞는 방향이 아닌가라는 생각을 가지기 마련이다. 그리고 보통은 현실적으로 어렵다는 결론에 도달하고 비프로그래머를 교육시키기를 포기한다. – git의 특성때문에 일반적인 버젼 관리 시스템(이하 VCS)보다 어려운 점들이 있다.

나도 과거에는 “git이 좋기때문에 개발을 잘 하기 위해서 공부해서 쓰는게 맞지만, 현실적으로 비프로그래머를 적응시키기는 어려운점이 많으니 다른 방법을 생각해보자.”  로 생각했었다. 이건 정말 맞는 생각일까?

지금까지 내린 결론은 “잘못된 생각이였다”이다.(앞으로 무엇인가 또 바뀐다면 생각이 바뀔지는 모르겠다.)

git을 쓰는 최대장점은 쉬운 branch(작업 히스토리)라고 할 수 있는데(git을 익숙하게 활용하면 다른 시스템의 branch는 가짜 같다), branch가 빛을 발휘하기 위해서는 merge가 쉽고 의미있어야 한다. text기반의 소스는 merge라는 과정이 쉬우면서 의미가 있지만, binary 기반의 소스는 무조건 최신파일로 덮어 쓰는 것 밖에 할 수 있는게 없다. binary 작업자의 입장에서 쉬운 branch의 장점을 느낄 수 있을까? 서비스를 하기 위해서 branch가 필요한것은 맞지만, 다른 VCS정도의 branch만으로 충분한 것은 아닐까? 좀 더 발전된 형태의 뭔가가 있지는 않을까 고민해봐야 할 문제이지만, 적어도 text위주의 작업을 하는 프로그래머와는 달리 binary 작업 위주의 아티스트에게 쉬운 branch의 의미는 없을 것이고, git을 훌륭하거나  편리한 도구라고 느낄 이유도 없는 것 아닌가라는 생각이 든다.

다시한번 생각해 보면서… git의 장점은 프로그래머의 입장이 아닐까? 아래링크도 참고해보자.

http://enemyhideout.com/2016/06/why-git-is-not-good-for-games/

https://www.quora.com/Why-isnt-Git-used-by-more-video-game-developers

 

std::vector로 배우는 레벨업 #2

01/18/2018 c++, programming No comments , ,

std::vector로 배우는 레벨업 #1에 이어서…

resize를 구현해 보자

아래 테스트 코드를 통해 동작을 확인해보자.

 

std::vector로 배우는 레벨업 #1

01/17/2018 c++, programming No comments , ,

선행학습: std::vector의 size와 capacity에 대해서 제대로 알고있자

std::vector의 push_back과 reserve를 한번 직접 구현해보자..

언뜻 생각해보면 template에 대해서 조금만 알고 있으면 다음과 같이 쉽게 구현 할 수 있을 거라는 생각이 들 것이다.

한번 다음의 테스트 코드로 std::vector과 MyVector의 출력결과가 어떻게 다른지 확인해 보면….

push_back을 했을때 복사생성자가 호출되느냐 대입연산자가 호출되느냐는 c++ 버젼에 따라 차이가 있을 수 있으니, 동일하다고 치고,

stl버젼은 reserve를 했을때 생성자가 호출되지 않지만, MyVector는 생성자가 2번 호출되는 차이점을 볼 수 있다.

이 차이가 의미하는 것은 무엇일까?

 

눈치가 빠른분들은 이미 감을 잡으셨을 것이다.

그렇다. vector에서 reserve함수를 통하여 capacity의 크기를 늘린다는 것은 메모리공간만 할당하는 것이지, 실제 객체가 생성되는 것은 아니다.

혹시, 아직 c++에서 객체 생성의 의미를 정확히 모르고 있었다면, 이제는 정확히 이해 했을 것이다.

(잘 모르겠으면 ‘c++에서 malloc과 new의 차이점‘을 읽어보자)

 

그럼 한번 요구사항에 맞게 코드를 고쳐보자

이제 std::vector와 동일한 결과가 나옴을 확인 할 수 있을 것이다.

c++에서 malloc과 new의 차이점

01/17/2018 c++, programming No comments

제일 중요한 포인트는 생성자의 호출 유무이다.

  • malloc
    • 동적 메모리를 할당한다.
  • new
    • 동적 메모리를 할당한다.
    • 생성자를 호출한다.

마찬가지로, free와 delete의 핵심 차이점은 소멸자의 호출 여부

기타 차이점으로는

  • 호출 하는 방법이 다르다.
  • new 의 경우에 메모리 할당에 실패하면 예외(std::bad_alloc)가 발생한다.

참고

https://msdn.microsoft.com/library/kftdy56f.aspx

http://en.cppreference.com/w/cpp/memory/new/operator_new

std::vector의 size와 capacity에 대해서 제대로 알고있자

12/11/2016 c++, programming 1 comment ,

std::vector를 제대로 알고, 제대로 쓰고 있는가를 파악할 때 내가 자주 하는 질문은

reserve와 resize에 대해 설명해 보세요.

의외로 많은 프로그래머가 제대로 대답하지 못한다.

저 질문을 대답하기 위해서는 size와 capacity에 대해서 잘 알고 있어야 하는데..

사실, std::vector을 사용하기에 앞서 어떻게 구현이 되어있을까? 라고 고민을 해보면 추측할 수 있는 문제가 아닌가 하는 안타까움이 들때가 많다.

이제 막 입문하는 사람은 그렇다 치고, 어느정도 사용하다보면, 당연히 가지게 되는 의문과 고민, 추측이 아닌가?

그것은 나만 기대하는 욕심인 것인가?

위 그림을 보면 대부분은 이해할 것이라고 생각한다.

capacity는 vector의 요소(element)들을 담을 수 있는 메모리가 할당되어 있는 공간의 용량이다.

size는 실제 유효한 요소(element)들의 갯수이다.

예를 들어, push_back함수를 통해 새로운 요소(element)를 맨뒤에 한개 추가할 때,

capacity > size보다 큰 상황이면 그냥 맨뒤의 공간에 요소를 복사해 넣고 ++size만 일어나며,

capacity == size 이면 capacity가 증가한 새로운 연속된 공간을 할당하고… 기존의 모든 요소들을 복사하여(기존 정보는 지우고..) capacity > size인 상황을 만들어 놓고, 맨뒤에 요소를 복사해 놓고, ++size를 하는 과정이 일어나는 것이다.

reserve는 이 capacity를 프로그래머가 원하는 크기로 할당을 해준다.

여기까지 size, capacity, reserve에 대해서 이해했으리라 믿고, Effective STL의 항목 14. reserve는 필요 없이 메모리가 재할당되는 것을 막아 준다.(Use reserve to avoid unnecessary reallocations)를 공부해 보길 바란다.

여기서는 resize에 대해서 조금 더 이야기를 해보려고 한다.

resize에 대해서 다음과 같이 설명하는 사람에게는 나는 70점을 줄 것이다.

지정한 수 만큼 요소를 만들어 준다. 내부에서 capacity가 작으면 재할당이 일어나며…

resize에서 한가지 더 중요한 것이 있는데, 증가되는 size만큼 복사생성자가 호출된다는 점이다.

(c++11까지는 복사생성자가 호출 됬었는데, 최신은 생성자가 호출된다.)

resize의 원형을 보자

C++ 11 이전 버젼이든 C++ 11 이후 버젼이든, 기본값을 따로 지정하지 않으면, default 생성자에 의해서 모든 요소가 초기화 된다는 사실은 동일하다.

과거에 이렇게 코딩한 사람을 본적이 있는데… 이는 resize의 스펙을 잘 모르고 한 바보같은….


참고

http://en.cppreference.com/w/cpp/container/vector/size

http://en.cppreference.com/w/cpp/container/vector/capacity

http://en.cppreference.com/w/cpp/container/vector/reserve

http://en.cppreference.com/w/cpp/container/vector/resize

Effective STL, 2001, Scott Meyers

 

가상 소멸자(virtual destructor)에 대한 정확한 이해

12/11/2016 c++, programming No comments

C++입문자들이 가상 소멸자에대해서 잘 모르는 것을 많이 봤다.

또, 어느정도 했다는 프로그래머중에서도 정확히는 모르는 사람들이 꽤 있드라…

“Effective C++ 항목7” 에 이 부분에 대한 설명이 있지만, 초보자들이 이해하기에는 다소 어려움이 있어 보여, 이부분에 대해 이해도를 높여 보고자 한다.

(다형성을 가진 virtual class에서) 가상 소멸자(virtual destructor)를 사용하지 않으면 문제는 무엇인가?

라는 질문을 했을때 다음과 같은 대답을 한다면 70점을 줄 것이다.

메모리 릭이 발생합니다

소멸자의 호출여부를 제대로 알고 있어야 100점이라고 할 수 있다. 대략

(파생 클래스의) 소멸자가 제대로 호출되지 않을 수 있어, 메모리릭을 포함해서 부분 소멸될 수 밖에 없는 문제점이 발생할 수 있습니다

C++은 생성자, 소멸자가 굉장히 중요하다는 것을 잊어서는 안된다.

생성자에서 락일 걸고, 소멸자에서 락을 해제하는 방법을 사용하며, 생성자에서 파일을 열고, 소멸자에서 파일을 닫는 방법을 사용하기도 한다.

위의 예에서 소멸자가 호출되지 않는다면 일어나는 문제점은 잘 알 수 있을 것이다.

메모리 릭이 발생한다고 대답하는 것은 왜 70점짜리 대답인지 자세히 알아보면,

Abstract의 소멸자에는 virtual 키워드가 없다.

이를 상속받은 Foo, Bar 2개의 class가 있는데,

Foo 클래스는 메모리릭 문제가 존재하지 않는다.

왜냐하면, Foo가 가지고 있는 데이터는 순전히 HeapManager에 의해서 할당/해제가 되기 때문에 소멸자의 호출여부와는 무관하기 때문

소멸자에서 하는 일이라고는 “~Foo()”를 출력하는 것 뿐

하지만, Bar 클래스는 메모리릭 문제가 발생 할 수 있다.

왜냐하면, char[]에대한 해제는 소멸자가 호출되어야만 발생 할 수 있기 때문

VS2015의 Heap Profile을 이용해서 확인해 보면,

~Foo()의 소멸자 호출과는 관계없이 메모리가 해제되는 2번째 파동을 볼 수 있다.

하지만, ~Bar()의 소멸자가 호출되지 않고 메모리가 해제된다면 생기는 4번째 파동은 볼 수 없다.

정상적으로 Abstract의 소멸자에 virtual을 붙인다면 어떨까?


참고

Effective C++, Third Edition, 2005. Scott Meyers

tuple의 모든 타입이 arithmetic type 인지 알아내는 템플릿 클래스

09/13/2016 c++, programming No comments ,

최근에 C#으로 코딩좀 하고 있는데…

역시 c++의 강력한 template이 그립다..

개인적으로 OOP형태의 언어에서는 c++의 template 때문에 c++을 버릴 수 없을 것 같다. ㅠ

필요할 것 같아서 작성했는데…

std::is_arithmetic 은 integral type(short, int, __int64 …) 이나 floating-point type(float, double) 이면 true이다.

이걸 tuple의 모든 항목이 true인지 검사하는 템플릿 클래스를 만들어 보았다.

참고로, std::is_arithmetic< std::tuple<…> >::value = false 이다.

응용하면 std::is_integral, std::is_floating_point, std::is_same 등도 만들 수 있다.

VS2015기준으로 작성되어있다.