hyeonk lab

블로그 이미지

hyeonk

hello world! hyeonk lab.

'전체'에 해당되는 글 44건

제목 날짜
  • 마우스 이벤트 중복 클릭 방지 함수 2017.05.29
  • xcode 8에서 code sign error 발생 시 해결 2016.11.17
  • 프레임워크란? 2016.07.26
  • java의 List와 반복문(loop), 그리고 변수 선언 위치에 대해서2 2015.11.05
  • 프로그래밍에서 SIDE EFFECT 란? 2015.10.14
  • boost 라이브러리 집중 분석 2015.10.13
  • svn 능력자를 위한 git 개념 가이드 2015.08.20
  • [강좌/메시지] WM_NOTIFY 메시지에 대해서...(ON_NOTIFY) - 2편 2015.08.19
  • [강좌/메시지] WM_NOTIFY 메시지에 대해서...(ON_NOTIFY) - 1편 2015.08.19
  • 10년 만에 새로 태어나는 ‘모던 C++’ 2014.11.04

마우스 이벤트 중복 클릭 방지 함수

mfc & winAPI 2017. 5. 29. 11:16

실행중인 기능을 마칠때 

함수 인자의 메시지 범위 마우스 클릭 관련 메시지들을 삭제해버린다.

메시지 범위는 수정이 가능.


MSG _msg;

while( PeekMessage( &_msg, NULL, WM_LBUTTONDOWN, WM_MBUTTONDBLCLK, PM_REMOVE ) );


* 주의: 예약된 중복 클릭을 이것으로 방지 하더라도, 다른 ui부분에서 또 발생이 되면 처리가 곤란해질 수 있다.

돌발적인 지연은 어떤 ui 요소에서건 발생할 수 있기 때문에, 꼭 이 방법이 정답이 될 수는 없다.

그럴 경우엔, 메시지를 삭제하지말고, flag를 통한 구분으로 무시할 메시지는 흘려보내는게 더 좋을 수 있다.

저작자표시 동일조건 (새창열림)

'mfc & winAPI' 카테고리의 다른 글

[강좌/메시지] WM_NOTIFY 메시지에 대해서...(ON_NOTIFY) - 2편  (0) 2015.08.19
[강좌/메시지] WM_NOTIFY 메시지에 대해서...(ON_NOTIFY) - 1편  (0) 2015.08.19
#import msado15.dll이 소스에서 OS등에 따라 컴파일이 되지 않을 때  (0) 2014.04.11
BOOL PreTranslateMessage(MSG* pMsg)  (0) 2014.03.21
분할 윈도우 중 하나에 접근하는 방법  (0) 2014.02.10
Posted by hyeonk

xcode 8에서 code sign error 발생 시 해결

ios 2016. 11. 17. 01:43

요즘 열심히 ios 를 열심히 공부 하고 있는데, 예제 따라 하던 중

빌드를 하니 별다른 이유없이 build failed가 출력되며, 웬 에러가 떴다.


Command /usr/bin/codesign failed with exit code 1


겁나 삽질 했다.. 검색하니 어떤분의 포스팅으로 해결할 수 있었다.

해결 방법은 다음과 같다.

터미널 열고,


cd /Users/USER_NAME/Library/Developer/Xcode/DerivedData

으로 이동한 후에 ( USER_NAME을 자신의 것으로 변경 )


xattr -c *

xattr -rc .

위 두 개의 명령를 실행한다.



내 경우에는, 예제를 따라 하던 중 리소스 폴더/파일들을 추가하고 빌드하니 이 에러가 나왔다.


해결하니 기쁘다..


끝.



참고자료: http://www.digipine.com/5130

저작자표시 동일조건 (새창열림)
Posted by hyeonk

프레임워크란?

개발이야기 2016. 7. 26. 23:41


프레임 워크란 단어처럼 많이 쓰이면서도 애매한 단어가 없는 것 같다. 

 

일단 구글링을 해 본 결과 다음과 같은 정의를 찾을 수 있었다.

 

 

=============================================================================

 

GoF의 디자인 패턴으로 유명한 랄프 존슨(Ralph Johnson) 교수는 프레임워크를

 

"소프트웨어의 구체적인 부분에 해당하는 설계와 구현을 재사용이 가능하게끔

 

일련의 협업화된 형태로 클래스들을 제공하는 것"

 

이라고 정의하였다.


프레임워크는 라이브러리와 달리 애플리케이션의 틀과 구조를 결정할 뿐 아니라,

 

그 위에 개발된 개발자의 코드를 제어한다.

 

프레임워크는 구체적이며 확장 가능한 기반 코드를 가지고 있으며,

 

설계자가 의도하는 여러 디자인 패턴의 집합으로 구성되어 있다.

 

==============================================================================

 

 

아, 저 말이 바로 이해가 간다면 굳이 다음 글을 읽을 필요는 없다.

 

이 글은 저 말을 이해하지 못하는 사람들 (나 자신을 포함하여) 을 위해

 

같은 이야기를 길게 풀어놓은 것에 불과하니까 말이다.

 

그런 분은 그냥 조용히 뒤로 가기 버튼을 눌러 주시거나,

 

가볍게 읽으시고 이 글에 잘못된 점이나 미비한 부분을 바로 잡아주셔도 된다.

 

 

위에서 프레임 워크와 라이브러리를 비교한 글귀를 찾을 수 있듯이,
 
일단 프레임 워크를 라이브러리의 연장선상에서 생각하는 것으로

 

프레임워크에 대한 이해를 시작해 보자.

 

 

라이브러리의 정의는 다들 대강은 알다시피

 

자주 쓰일 만한 기능들을 모아 놓은 유틸 (클래스)들의 모음집 정도로 정의할 수 있겠다.

 

 

(이 과제를 내준 형의 말대로)

 

사용자(프로그래머)와 실제 구현하고자 하는 기능 사이에,

 

사용자로 하여금 구현하고자 하는 기능을 쉽게 쉽게 제공해주는 중간 계층이란 면에 있어서

 

라이브러리와 프레임 워크는 일견 비슷한 점이 있다.

 

 

음.. 그렇다면 그냥 라이브러리라고 부르면 되지 거창하게 프레임 워크로 구분지어 부를 필요는 뭐냐?

 

대체 차이가 뭐냐? 라는 질문이 나올 법 하다.

  

 

프레임 워크와 라이브러리의 가장 큰 차이점이라 할만 한건

 

프레임 워크에는 라이브러리에

 

뼈대가 되는 클래스들과 그 클래스들의 관계로 만들어진

 

일종의 '설계의 기본 틀'이 추가된다는 점일 것이다.

 

여기서 쓴 혹은 설계 틀이란 말은

 

물론 위의 정의에서 나온 '확장 가능한 기반코드'라든지, '재사용 가능한 형태의 협업화된 클래스들' 이라는 말과 같은 뜻이다. 

 

 

자, 우선 라이브러리부터 생각 해보자.

 

라이브러리가 아무리 날고 기어봤자 라이브러리는 라이브러리다.

 

라이브러리가 설계를 대신해주진 않는다.



뭔 말이냐면, 라이브러리는 그저 프로그래머가 프로그램을 짜다가

 

'아, 요럴 땐 라이브러리에서 이런 기능을 뽑아다 쓰면 되겠구나'

 

라는 생각이 들었을 때, 그때 그때 필요한 걸 가져다 쓰는 대상이지

 

라이브러리가 그 기능을 쓰기 위해 필요한(혹은 효율적인)

 

구조에 대해 말해주지는 않는다는 뜻이다.

  

 

따라서, 동일한 라이브러리를 쓰는 동일한 기능의 프로그램일지라도

 

클래스 관계 구조나, 데이터를 처리하는 절차라든지,

 

프로그램이 화면에 그려지는 방식 같은 등등의 요소들은 

 

프로그램을 짜는 프로그래머마다 천차만별 일수 밖에 없으며

 

프로그램을 완성하는 데에 걸리는 시간도, 완성된 코드의 품질도

 

프로그래머의 역량에 따라 제각각일 것이다.

 

 

이제 프레임워크를 보자.

 

보통 프레임워크엔 프레임 워크의 제작자가 '이걸 기초로 해서 만드세염' 이라고

 

지가 만들어 놓은 '기반 코드'가 있다.


 

물론 이 코드, 혹은 클래스들은

 

차후 사용자들에 의해 확장될 것을 충분히 고려해서 만들어졌기 때문에

 

사용자 입장에서는 그저 이 것을 가지고,

 

여기 저기를 자기 입맛대로 바꾸고

 

살을 덧붙여 자기만의 프로그램을 완성해 나가면 되는 것이다.

 

 

왜, 비주얼 스튜디오를 켜서 새 MFC 프로젝트를 열고

 

다이알로그 기반 -> 확인 만 눌러도

 

일단 기본적으로 돌아가는 다이알로그 박스가 완성되고,

 

 

사용자가 만약 여기다 그리기 기능을 추가하고 싶다, 하면

 

상속 받은 클래스에 OnPaint() 함수를 재정의해서

 

단순히 함수 몸체 안에 코드를 쳐 넣기만 하면 되는 것처럼 말이다.

 

 

이러한 샘플, 즉 '확장 가능한 기반 코드'에는

 

프레임 워크 제작자 나름대로의 설계 철학이 담겨 있으며,

 

차후 이 프레임 워크의 사용자가

 

제작자가 설계한 구조를 유지하면서 확장할 수 있도록,

 

제작자에 의해 의도된 제약 사항이 존재한다.

 

 

아까 그 사용자가

 

OnPaint() 외에 다른 방식으로 그리기 구현을 해보고 싶다거나

 

혹시라도 OnPaint()가 호출되는 시점을 

 

자신이 통째로 제어해보고 싶다던지 하는 금지된 욕망을 가지게 되면 

 

프로그램 짜기가 상당히 껄쩍지근해 지는 것처럼 말이다.

 

(물론 불가능한 것은 아니며, 또 꼭 필요할 경우도 있기는 하지만,

 

이는 프레임워크를 사용할 때의 장점인 신뢰성과 비용 절감을 떨어뜨릴 수 있는 요소이다.)

 

 

이런 식으로 사용자에게 가해지는 제약 사항은

 

(특히 그 제약 규칙이 구조에 대한 것일 경우)

 

흔히 디자인 패턴을 구현한 코드의 형태로 표현되는데,

 

이 중 가장 흔하고 쉽게 찾아 볼 수 있는 예가 MVC 패턴이다.

 

 

MFC의 SDI(단일 문서 프로젝트)를 열어보면

 

처음부터 C~~~View와 C~~~Document의 두 클래스가 생기는 걸 볼 수 있는데

 

 

이 것은 이 프레임워크의 제작자가

 

MVC 패턴을 구현한 클래스인 CView와 CDocument를 기반 코드로 제공함으로써

 

 

자기가 만든 프레임 워크를 사용하여 만들어지는 프로그램들은

 

(MVC 패턴을 쓰는 이유인) '데이터의 연산 과 '데이터의 표현'이 서로 분리 되어야 한다는 것을

 

사용자에게 명시한 것이라고 보면 되는 것이다.

 

 

결론적으로 기반 코드는

 

프레임웍 제작자가 사용자들로 하여금

 

세세하게 신경 쓰지 않아도 쉽고 빠르게 기능을 확장하거나 유지보수할 수 있게 해주는 

 

구조에 대한 가이드라인 이나,

 

혹은 함부로 건들 경우 프로그램이 자칫 '신비롭게' 동작할 수 있기 때문에

 

제약을 가하고 싶은 사항 등을

 

여러 디자인 패턴을 조합해 표현해 놓은 결과라고 보면 된다. 

 

 

이 말은 다른 클래스의 부모가 되는 베이스 클래스를 짜본 경험이 있다면

 

쉽게 이해 할 수 있을 것이다.

 

 

다른 클래스들의 기초가 되는 기반 클래스의

 

각 멤버의 캡슐화 정도,

 

멤버의 상수화 여부,

 

다른 클래스들과의 관계,

 

함수 멤버의 가상화 여부 같은

 

만들 땐 별거 아닌거 같았던 자잘한 요소들이

 

후에 말단에서 실제로 사용되는 클래스들의 구현에 지대한 영향을 미치기 때문이다.

 

 

별 생각 없이 기반 구조를 짰다가

 

나중에 말단 클래스에서

 

어떤 기능을 구현하기가 참으로 난감한 경우가 생겨

 

기반 구조를 첨부터 다시 뜯어 고쳐야 했던 경험이 다들 한 번쯤은 있을 것이다.

 

 

여튼, 얘기가 약간 곁가지를 탄 느낌이 있는데 정리하자면

 

 

프레임 워크란

 

설계의 기반이 되는 부분을 기술한  확장 가능한 기반 코드와

 

사용자가 이 코드를 자기 입맛대로 확장하는 데 필요한 라이브러리

 

이 두 가지 요소가 통합되어 제공되는 형태를 말하며,


 

 

사용자가 이를 이용해

 

일정 수준 이상의 품질을 보장받는 코드를, 비교적 빠른 시간에 완성 및 유지 보수할 수 있는

 

환경을 제공해주는 솔루션으로


"기본적인 설계나 필요한 라이브러리는 알아서 제공해 줄꺼니깐

 

넌 그냥 니가 진짜로 하고 싶은 기능 구현에만 전념해!"

 

라는 취지에서 만들어진 물건이란 것이다.



출처 - http://blog.naver.com/PostView.nhn?blogId=sleepy1027&logNo=150085034164


저작자표시 동일조건 (새창열림)

'개발이야기' 카테고리의 다른 글

프로그래밍에서 SIDE EFFECT 란?  (0) 2015.10.14
boost 라이브러리 집중 분석  (0) 2015.10.13
개인 프로젝트를 위한 프로젝트 매니지 먼트와 오픈소스  (0) 2013.05.07
윈도우 서버 환경에서, 최대 생성 가능한 소켓(socket) 연결 수는 얼마일까?  (0) 2013.04.16
Posted by hyeonk

java의 List와 반복문(loop), 그리고 변수 선언 위치에 대해서

java 2015. 11. 5. 13:14

출처: http://egloos.zum.com/benelog/v/1382604


수정이력

2007/08/13  

1. '들어가며', '마치며' 대폭 수정 

2. 오타 수정  : 여러개의 메소드에서 같이 써는 코드였었습니다.==> 여러개의 메소드에서 같이 쓰는 코드였었습니다. 

 

2007/08/09

  1. 첫 단락 소제목 변경 ( "ArrayList나 Vector에는 iterator가 성능이 더 안 좋다."에서 ) 

 

2007/08/08

  1. 소제목을 h3태그로
  2. iterator를 쓸때 고려할 점 설명에 내용 보강
  3. 반복될 필요가 없는 코드의 예 설명에서 오타수정, 문장 다듬기
  4. 바이트 코드설명 부분의 한 문장 추가, 문장 다듬기

 

들어가며

 저는 요즘 유지보수 프로젝트에서 일하고 있습니다. 많은 시간을 코드를 다듬는데 쓰고 있고  다른 개발자들의 코드를 많이 볼 수 있는 기회가 되고 있습니다.  그러다 보니 List의 크기만큼 반복문을 돌리는 코드에서 많은 개발자들은  루프 블럭 안에서만 쓰는 변수의 선언을 밖에다 하고 있다는 것을 발견하게 되었습니다.  이에 대해서 개발기간에 대화를 해볼 기회가 없었다는 것이 아쉽게 느껴지더군요. 그래서 그 내용과 함께 list와 loop에 관한 몇가지 이야기들을 같이 묶어서 글로 정리해 보게 되었습니다. 첫번째 소제목과 두번째 소제목 아래의 내용은 성능을 약간이라도 더 개선하고 싶을 때 도움이 될 정보들이고, 세번째 소제목에서 위에서 말한 많은 개발자들의 습관이 실제로는 성능에는 아무 영향이 없음을 설명할려고 했습니다.

 

ArrayList와 Vector는 RandomAccess inteface를 구현하고 있다.

List의 크기만큼 반복문을 도는 방법에는 크게 두 가지 방법이 많이 쓰이고 있죠.

1. java.util.List의 size()로 크기를 구해서 그 갯수만큼 반복문을 돌아서 get(int index)로 List안에 있는 객체를 가지고 온다.

2. iterator() 로 java.util.Iterator 객체를 얻은 후 이 객체의 hasNext()가 true인 동안 반복문을 돌아서 next()로 List안의 객체를 가지고 온다.

 

그런데 그 객체가 java.util.ArrayList나 java.util.Vector가 확실할 때는 1번의 방법이 더 빠르다고 합니다. 그 이유는 API문서를 보시면 확인할 수 있듯이 ArryList와 Vector는 java.util.RandomAccess interface를 구현하고 있기 때문입니다. RandomAccess의 API문서에는 아래와 같이 적혀있습니다.

 

Marker interface used by List implementations to indicate that they support fast (generally constant time) random access.

 

List의 구현체가 빠른 임의접근을 지원한다는 것을 나타내는 표시 interface라는 말입니다. 선언되어 있는 메서드도 아무것도 없지요.

API문서에는 이렇게 적혀있지만 성능이 항상 우선순위의 가치는 아니므로 RandomAccess List에는 get(int index)을 써야 한다는 법칙을 만들 수는 없을 것입니다. 가령 List와 다른 Collection 객체, 배열 등을 한꺼번에 묶어서 처리할 때는 iterator pattern이 유용합니다. (참고자료5: Head first design pattern).

하지만 성능이 중요한 코드를 짜는 경우에는  객체를 생성하는 부분에서 ArrayList나 Vector를 생성한다는 정보를 확실히 가지고 있고, 그것이 LinkedList같은 다른 객체로 변경될 가능성이 없다면 java.util.RandomAccess interface의 명세는 무시할 수만은 없을 것입니다.

List 처리  시에 성능을 극대화 시키기 위해서 RandomAccess interface를 구현했는지 검사해서  get(int index)를 쓸지 iterator()를 사용할지 결정하는 코드도 있기는 합니다. (참고자료2의 자바퍼포먼스 튜닝 중).  보통 그렇게까지 성능을 쥐어짤 경우는 흔치 않겠죠.

 

List의 크기를 반복해서 구할 필요가 없다.

위에서 1번으로 제시된 것처럼 get(int index) 를 쓰는 방법을 쓸 때도 아래와 같은 코드를 많이 보게 됩니다.

 

for( int i = 0; i < list.size(); i++){

//일하기

}

 

흔하게 보는 코드죠? 그런데 위 코드에도 굳이 필요없는 성능의 손실이 있습니다. 바로 list의 크기를 구하는 size() 메서드가 매번 반복해서 호출된다는 것입니다. 반복문 내에서 list의 크기가 변하는 경우가 아니라면 for문의 초기화 때 한번으로 충분합니다.

for(int i = 0, n = list.size(); i < n; i++){

//일하기

}

 

n을 for문 앞에서 선언하는 방법도 있지만, for block 밖에서 n이 필요한 경우가 아니라면 n이 for의 초기화 부분에 선언되고 할당되는 것이 좋습니다. 변수의 유효범위가 최소화되기 때문이죠.

 

Loop안에서만 사용하는 변수의 선언을 loop밖에 해야 할까?

코드의 중복을 없애라거나 적절한 API를 쓴다거나 등의 바람직한 코드를 위한 지침에는 항상 다음과 같은 이유들이 붙어다닙니다.

  • 코드의 가독성이 좋아진다.
  • 유지보수가 편해진다.
  • 오류의 발생 가능성이 줄어든다.

 

지역변수의 유효범위를 최소화하라는 지침에도 위의 이유들이 역시나  인용되고 있지요. (참고자료1 Effective Java, 참고자료 3 Code completed 2nd Edition). 변수의 선언과 초기화, 사용 사이에 있는 코드가 많을 수록  취약성 있는 코드가 들어갈 여지는 커집니다. 이른 시점부터 쓰이지도 않는 객체가 초기화되어서 메모리를 낭비하고 있을 가능성도 있고, 선언되고 한번도 쓰이지 않는 변수들도 눈에 잘 들어오지 않게 됩니다. (뭐 쓰이지 않는 변수는 Eclipse의 warning으로 잡아낼수 있기는 합니다만)

 

지역변수를 쓰기 바로 전에 초기화하고, 선언과 함께 초기화 하는 좋습니다. 즉 가능한 변수가 처음 사용되는 곳의 가장 근접한 위치에서 선언되고 초기화되어야 한다는 말이죠. 변수의 수명을 가능한 짧게 유지하라는 말로도 표현됩니다. 하지만 많은 개발자들은 메소드의 처음에서 C언어처럼 쭉 변수를 선언하고 시작하고 있습니다.

 

 반복문에서는 while보다는 for를 쓰는 것이 변수의 범위관리에 유리합니다.

 

 보통 iterator와 while이 아래와 같이 많이 쓰이고 있습니다.

Iterator i = c.iterator();

while (i.hasNext()){

 doSomething(i.next());

}

 

 이 것을 for문으로 쓴다면 다음과 같습니다.

for (Iterator i = c.iterator() ; i.hasNext(); ) {

 doSomething(i.next());

}

 

작은 차이지만 Iterator i는 for block을 벗어나는 순간  잊어버려도 되는 것이니 block 밖에서 개발자의 머리는 조금이나마 가벼워 질 수 있습니다. 이것은 캡슐화의 원칙인 class의 맴버 중 밖에서 볼 필요 없는 것들은 private으로 선언해야 하는 이유와 일맥상통합니다.

 

그렇다면 다음의 경우는 어떠할 까요?

 1. 루프밖에서 list에서 꺼내서 담을 변수 선언

    static int countOfIncluded(List list, String str){
        int count = 0;
        String element;
        for (int i=0,n=list.size();i<n;i++){
           element = (String) list.get(i);
            if (element.indexOf(str)!= -1 ) count++;             
        }
        return count;
    }

 

  2. 루프안에서 list에서 꺼내서 담을 변수 선언

    static int countOfIncluded(List list, String str){
        int count = 0;

        for (int i=0,n=list.size();i<n;i++){
           String  element = (String) list.get(i);
            if (element.indexOf(str)!= -1 ) count++;             
        }
        return count;
    }

 

 많은 분들이 1번과 같이 코드를 작성하고 있고, 1번이 성능이 좋다는 '믿음'을 가지고 계실 것입니다. 실제로 한 번 테스트 해볼까요? 크기가 10000개인 리스트를 생성해서 100번씩 반복해서 실행시간을 찍어보는 코드를 만들어보았습니다. 1번 방법은 ListReader1.java, 2번 방법은ListReader2.java, 시간을 찍어보는 코드는 LoopTester.java 로 첨부하였습니다. 이런 비교 시에는 실행순서에 따라서도 실행시간이 영향을 받으므로 1,2,2,2,1,1,2의 순서로 몇번씩 사이에 걸린 시간을 밀리세컨드로 출력하게했습니다. 실행결과는 다음과 같습니다.

test1:밖에 선언 2243
test2:안에 선언 2153
test2:안에 선언 2184
test1:밖에 선언 2253
test1:밖에 선언 2213
test2:안에 선언 2123

 

 거의 차이가 없거나 오히려 안에 선언한 쪽이 미묘하게 빠르기도 합니다.

이번에는 javap -c 를 이용해서 컴파일된 byte 코드를 분석해 보겠습니다. 결과는 첨부파일로 넣었으나 알아보기 쉽도록 diff로 비교한 화면을 캡쳐했습니다.

ListReader1ByteCode분석.txt

ListReader2ByteCode분석.txt

byteCodeDiff.JPG

 

노란 줄이 많은 것은 라인수가 1라인 차이가 나고, local variable이 저장되는 공간의 index번호가 달라서입니다. 내용을 보시면 거의 똑같이 실행되고 있는 것을 알 수 있으실 것입니다.  루프가 도는 goto문장을 봐도 (왼쪽의 47라인과 오른쪽의 46라인) 같은 곳으로 (13라인과 12라인)으로 이동을 하기 때문에 특별히 오른쪽 예제2의 경우가 더 일을 하는 것은 없습니다. 다만 3번째 라인 istore n 이였던 것이 istore_n 으로 바뀌는 등 언더바(_)가 들어간 부분이 있습니다.

 

각각의 명령어의 의미는 다음 링크를 확인해 보시면 나와있습니다.

  • istore
  • istore_n
  • iload
  • iload_n
  • 명령어의 인덱스페이지

내용을 보면 istore과 istore_n은  전자가 암시적이라는 것만 빼고는 같다고 나옵니다. (Each of the istore_<n> instructions is the same as istore with an index of <n>, except that the operand <n> is implicit. ) iload의 경우도 마찬가고요.  어떤 차이가 있을까 해서 검색해 봤더니 아래와 내용을 발견했습니다.

'istore_<n>' is functionally equivalent to 'istore <n>', although it is typically more efficient and also takes fewer bytes in the bytecode

원본링크

 

istore_n 쪽이  오히려 효율적이라는 말이 나와 있습니다. 만약 iload_n도 마찬가지라면 goto가 찾아가는 라인에서는 iload_n이 있는 예제2가 더 효율적인 코드일 수도 있다는 것입니다. 어쨓든 이런 작은 차이를 접어둔다면 코드가 하는 일의 절차는 차이가 없습니다.


 결국 "루프 안에서 반복되는 변수 선언을 밖으로 빼는 것은 성능상에 아무런 이점이 없고 소스에서 변수의 유효범위만 늘어나게 한다. " 는 것입니다.

 

 여기서 이런 말을 하실 분이 계실 것 같습니다.

"원래는 변수를 loop안에 생성하는 루프가 돌 때마다 String 객체의 참조를 저장하기 위한  공간이 따로 할당되는 것인데 위의 경우는 JVM에서 최적화를 해 준 것 아니냐?   JVM에 따라서 이런 최적화가 안 되는 버전도 있을 수가 있는데 개발자는 어떤 JVM에서도 한번만 선언이 되도록 루프 밖에 변수를 선언해야 하지 않겠냐? "

 

그러나 Java™ Virtual Machine Specification, The, 2nd Edition 이라는 책을 보면 다음과 같은 내용이 있습니다.

 

The sizes of the local variable array and the operand stack are determined at compile time and are supplied along with the code for the method associated with the frame .

 

위의 문장은 메소드가 호출될 때 생성되는 저장공간인 frame에 대한 설명에서 인용한 것입니다. (원문링크 :http://java.sun.com/docs/books/vmspec/2nd-edition/html/Overview.doc.html#15722)

 풀이하면  메소드 안의 local variable들의 값들은 고정된 크기의 배열에 저장되고 그 배열의 크기는 compile시에 결정된다는  내용이 있습니다. 즉 한 메소드 안에서 사용할 local variable이 저장될 공간의 갯수는 이미 compile 시에 정해져 있는 것이지 동적으로 변하는 것이 아니라는 말입니다. 만약 위의 소스에서 list의 갯수에 따라서 local variable의 값이 저장되는 공간(객체가 저장되는 공간을 가리키는 말이 아닙니다.)이 달라진다면 위의 설명과 모순되는 일입니다.

 

 비슷한 설명과 논쟁이 인터넷의 여러 곳에서 이미 벌어졌으니 아래의 링크를 읽어보셔도 재밌을 것입니다.

  1. local variables in Java : http://rmathew.blogspot.com/2007/01/local-variables-in-java.html
  2. Myth - Defining loop variables inside the loop is bad for performance : http://livingtao.blogspot.com/2007/05/myth-defining-loop-variables-inside.html
  3. http://forum.java.sun.com/thread.jspa?threadID=707455&messageID=4098210
  4. http://weblogs.java.net/blog/ddevore/archive/2006/08/declare_variabl_1.html
  5. http://www.theserverside.com/news/thread.tss?thread_id=41857

 

1,2번 링크가 바이트코드 역어셈블 결과와 함께 내용을 잘 풀어서 설명하고 있습니다. 3번 링크에서 Dru Devore는 primitive type일 때는 똑같고 Object type일때는 다르다는 결론을 쓰고 있으나, 사실 그가 든 object type의 예제는 '객체를 한번 생성하느냐' 대 '반복해서 생성하느냐'의 문제이므로 경우가 다릅니다. 3번 링크의 댓글에서 다른 사람들도 그 점을 지적하고 있고 2번 링크의 글에서도 관련 설명이 있습니다. 어쨓든 루프밖에 있으나 안에 있으나 바이트코드의 실행과정은 같고 성능은 차이가 없다는 내용은 모든 페이지에 다  있군요.

 

이런 오해들은 전에 Java의 호출은 pass by value 에서 말했던 객체선언에 대한 오해와 어느 정도 관련이 있다고 봅니다. 객체를 생성이나 사용을 안 하더라도 선언 자체만으로 저장을 위한 큰 공간이 할당된다고 믿는 경우가 많은 것 같습니다.

 

전에 이런 코드를 본적이 있습니다. 클래스의 멤버 변수로 임시 객체를 생성해 놓고 그것을 여러개의 메소드에서 같이 쓰는 코드였었습니다.

Class MyClass{

String  temp;

void work(List list){

 for (int i=0; i = list.size(); i++){

temp = (String) list.get(i);

//기타...

}

}

void play(List list){

 for (int i=0; i = list.size(); i++){

temp = (String) list.get(i);

//기타...

}

}

}

 

이런 코드를 짰던 사람도 경력도 어느 정도 되고, 실력도 있어보이는 사람이였죠. 아마 이 개발자는 이런 구조가 temp 객체가 저장될 공간을 상당히 아꼈다는 뿌듯함을 가지고 있었을 지도 모릅니다. 그러나 객체는 Heap 메모리에 있는 것이기에 지역변수가 선언이 하나 덜 되었다고 해서 객체가 줄어든 것은 아닙니다. 그리고 여기서는 list에서 받아오지 새로 생성되는 객체는 없습니다. 더군다나 설계상 의미로도 temp는 Class의 멤버로써 아무런 의미가 없습니다. 성능측면에서 봐도  클래스 멤버 변수는 지역변수에 비해서 더 비용이 큽니다. (참고자료 2: 자바 퍼포먼스 튜닝)

 

한편으로는 변수의 범위를 줄인다고 해서 반복적으로 Loop 안에서 실행해줄 필요가 없는 작업까지 loop안으로 끌고 들어가서는 안되겠죠. 다음과 같은 코드를 본적이 있습니다.

for (int i=0, n=userList.size(); i<n;i++){

UserBiz biz = (UserBiz) lookup(UserBiz.ROLE); //비지니스 컴포넌트 객체를 얻어온다.

   UserVO vo = (UserVO)userList.get(i);

biz.add(vo);

 }

 

위의 코드에서 비지니스 컴포넌트를 얻어오는 부분은 반복적으로 수행될 필요가 없으므로 loop 밖으로 나가는 것이 맞습니다.  위의 biz 객체를 loop안에서만 쓴다고 하여도 위와 같이 해당 코드가 안으로 들어가 있다면 한번만 얻어오는 되는 객체를 얻어오는 작업이 계속 반복될 것 입니다. 사실 위의 경우는 비지니스 객체에서 List를 받아서 처리하는 메소드를 하나 더 만드는 것이 나을 것 같네요. 아키텍쳐에 따라 다르지만 비지니스 객체의 호출은 비싼 작업일 경우가 많고 그런 호출의 횟수는 최대한 줄일수록 좋겠죠.

 

개선된 for문

Java 5의 개선된 for문을 아시는 분이라면 아래의 방법을 많이 쓰실 것입니다.

ArrayList<Integer> list = new ArrayList<Integer>();

for (Integer i : list) { ... }

 ( J2SE 5.0 in a Nutshell 에서)

보기에도 이쁘고, 변수의 유효범위가 loop을 벗어나지도 않는 좋은 문법이네요. ^^ 프로젝트에서 Java 5 이상을 쓰시고 계신다면 변수를 loop안에 넣느냐로 논쟁하기 전에 개선된 for문을 먼저 적용해 보는것이 나을 것 같습니다.

 

마치며

위의 내용을 묶어서 결론을 내리자면, "loop안에서만 쓰는 변수 선언을 밖으로 빼는 것은 성능에 아무런 영향이 없으며, 굳이 성능을 개선하고 싶다면 RandomAccess 인터페이스의 고려, 반복되는 list크기 계산의 제거 등을 먼저 신경쓰라. 그리고 Java5을 쓰고 있다면 개선된 for문을 쓰라."는 것으로  정리하고 싶습니다.

 

사실 성능에 대한 고려는 병목이 발견될 때 필요하다면 하는 것이 바람직한 순서겠죠. 개발자는 알아보기 싶고, 관리하기 쉬운 코드를 짜는 것에 먼저 집중을 해야 할 것입니다. 그리고 고용량의 CPU가 돌아가는 환경이라면 개발자가 성능을 고려해서 짠 코드와 그렇지 않은 코드의 차이도  대부분 사람이 인지하지 못하는 정도의 미미한 것일 가능성이 높습니다. (TA팀 이상민 선임님의 테스트와 조언). 그런 점에서 본다면 이 글에서 가장 강조되어야 할 내용은 '변수의 유효범위의 최소화'라고 생각됩니다.

 

다른 분들이 추가로 자료를 찾는 번거로움을 덜어 드릴려고 관련 자료를 되도록 인용하고 링크를 걸었습니다. 그렇게는 했어도 혹시나 제가 잘못 이해해서 오류가 있는 부분이 있는지도 모르겠습니다.  고수분들은 그런 부분을 발견하신다면 즉각 지적해 주시기 바랍니다~ ^^

그리고 이 글은 계속적으로 갱신해서 개선을  하고 있는 중입니다. 제가 가입해 있는 커뮤니티 사이트 중 두 곳에도 이 글을 올렸는데, 거기에서 조언을 주신 많은 분들께도 감사드립니다.

 

참고자료

Java API문서와 링크외의 책에서 참고한 내용입니다.

 

  1. Effective Java , Joshua Bloch저 이해일역
    •  7장 프로그래밍 일반 - 항목29 - 지역변수의 유효범위를 최소화하라.
  2. 자바퍼포먼스 튜닝, Jack Shirazi 저 서민구 역 
    • 11장 적절한 자료구조와 알고리즘   
      • 질의 최적화 - 불필요한 반복적 메소드 호출 제거
      • RandomAccess 인터페이스
    • 6장 예외 단언, 캐스팅, 변수 - 변수
  3. Code completed 2nd Edtion, Streve McConnell 저 서우석 역  
    • 10장 변수사용시 일반적인 문제 - 10.3 변수의 초기화에 대한 지침
  4. Head First Design Pattern
    • 9장 이터레이터와 컴포지트 패턴


저작자표시 (새창열림)
Posted by hyeonk

프로그래밍에서 SIDE EFFECT 란?

개발이야기 2015. 10. 14. 19:49

출처

: http://story.wisedog.net/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D%EC%97%90%EC%84%9C-side-effect-%EB%9E%80/


2013/09/17 WISEDOG
오늘은 프로그래밍에서의 ‘side effect’에 대해서 이야기하고자 합니다. 우리가 접하는 side effect라는 단어는 직역하면 ‘부작용’입니다. ‘부작용’이라는 단어를 들었을 때 대부분의 사람들은 약의 ‘부작용’을 떠올릴 것입니다. ‘부작용’이란 용어는 왠지 탈이 난것 같고, 일어나서는 안되는 꺼름직한 일이겠지만, 프로그래밍에서의 ‘side effect’는 꼭 부정적인 말은 아닙니다.
이런거 아님

Side Effect라는 단어는 보통 프로그래밍 언어의 스펙 문서나 코딩 표준 문서에 많이 나옵니다. 예를 들어 JSF Air Vehicle C++ Coding Standards의 187번째 규칙에 바로 이 단어가 나오는데요, 이 규칙은 아래와 같습니다.

빈 구문이 아니라면 반드시 하나의 side effect를 가져야합니다. (All non null statements shall potentially have a side effect.)

왜냐하면 빈 구문이 아닌데 side effect가 없는 구문은 일반적으로 프로그래밍 에러를 가리키는 것이죠.?그런데 side effect가 무엇이냐구요?

Side Effect 란?

ISO/IEC 14882는 side effect라는 용어를 다음과 같이 정의합니다.

Accessing an object designated by a volatile lvalue, modifying an object, calling a library I/O function, or calling a function that does any of those operations are all side effects, which are changes in the state of the execution environment.

쉽게 말해서 실행 중에 어떤 객체를 접근해서 변화가 일어나는 행위(라이브러리 I/O, 객체 변경 등)입니다.

예제를 한 번 보겠습니다.

x = 3 + 4;
위의 표현식은 1개의 side effect가 있습니다. x의 값이 변경되었기 때문입니다. .

y = x++;
위의 표현식은 총 2개의 side effect가 있습니다. x++에서 x가 한 번 변하고, x 값 대입으로 y가 한 번 변합니다.

3 + 4;
위 표현식은 side effect가 없습니다.

if(flag){
foo();
}

위 표현식은 flag가 true일 경우에 한해서 잠재적으로 side effect 가 있습니다.

Side Effect의 문제

Side Effect 자체는 아무 문제가 안되지만, 프로그래머가 이를 고려하지 않고 사용했을 경우 의도하지 않은 결과가 나올 수 있습니다.

1. 매크로에서 잘못 사용 ( CERT EXP31-C)

1
2
#define sqr(x) x*x
sqr(a+b)

매크로를 사용할 때 Side Effect를 고려하지 않고 사용한 예입니다. sqr 매크로는 들어오는 인자 값을 두 번 곱하도록 정의되어 있기 때문에 전처리를 거친 후, 코드는 아래와 같습니다.

1
a+b*a+b

프로그래머가 의도한 것은 분명 a+b의 값을 제곱ㅎ매크로를 사용할 때, 매크로 정의를 확인하지 않고 무심코 그냥 사용할 경우 위와 같은 참사가 나기 쉽습니다. 이 경우 문제를 찾아내기가 매우 힘듭니다. ?따라서 매크로를

1
#define sqr(x) ((x)*(x))

와 같이 정의하거나

1
2
long value = a + b;
sqr(value)

이렇게 매크로에서 Side Effect가 일어나지 않도록 사용해야 합니다.

2. sizeof 에서 잘못 사용(?AV Rule 166 , MISRA-C:1998 Rule 40, CERT EXP06-C)

sizeof는 어떤 변수나 타입의 크기를 알아낼 때 사용됩니다. 하지만 종종 아래와 같이 sizeof 안에 side effect를 일으키는 연산을 적는 경우가 있습니다. 아래 코드는 GCC 계열에서 동작하는 코드입니다. MSVC 의 경우 2008에서 컴파일 오류가 나는 코드죠. : )

1
2
int a = 14;
int b = sizeof(a++);

하지만 VLA(Variable Length Array)의 [] 안의 연산자를 제외하고는 연산이 수행되지 않습니다. 따라서 안전하게 사용하려면 sizeof 에는 어떠한 side effect도 일어나지 않도록 프로그래밍 해야 합니다.

3. && 혹은 || 에서 사용(?AV Rule 157 , MISRA-C:1998 Rule 33, CERT EXP10-C)

논리적 AND연산(&&) 또는 논리적 OR연산(||)의 오른쪽 피연산자는 side effect를 포함하면 안됩니다. 조건에 따라 실행될 수도 실행 안될수도 있는데다가 연산 순서에 따라서 조건 결과가 달리나올 수 있기 때문입니다.

1
2
3
4
5
if ( logical_expression && ++x) // 문제
{
// some expression
}
function(x);

위의 코드의 경우 logical_expression이 false면 ++x가 자동으로 실행이 안되기 때문에 아래 function(x)의 경우 원하는 값이 제대로 나오지 않을 수 있습니다.


저작자표시 (새창열림)

'개발이야기' 카테고리의 다른 글

프레임워크란?  (0) 2016.07.26
boost 라이브러리 집중 분석  (0) 2015.10.13
개인 프로젝트를 위한 프로젝트 매니지 먼트와 오픈소스  (0) 2013.05.07
윈도우 서버 환경에서, 최대 생성 가능한 소켓(socket) 연결 수는 얼마일까?  (0) 2013.04.16
Posted by hyeonk

boost 라이브러리 집중 분석

개발이야기 2015. 10. 13. 16:03

출처: http://www.jiniya.net/wp/archives/11769


boost 라이브러리 집중 분석
by 신영진(YoungJin Shin), codewiz at gmail.com, @codemaru, http://www.jiniya.net

시쳇말로 C 언어는 2시간 배워서 20년 써먹고, C++은 20년 배워서 2시간 써먹는다는 이야기가 있다. 그만큼 C 언어는 간결하고 핵심적인 기능만 포함한 언어인 반면, C++은 복잡하고 다양한 기능이 포함돼 있다. 나는 C 언어를 배운지는 20년, C++은 13년 정도 됐다. 그간 주로 일했던 도메인이 보안이라 성능 이슈가 주요하고 네이티브 코드를 많이 작성해야 했기에 주력 프로그래밍 언어는 언제나 C++이었다. 그렇게 오래 함께한 언어임에도 C++은 여전히 어려운 언어이고, 지금까지도 항상 공부해야 하는 언어다. 물론 이런 현상은 단지 내가 멍청하기 때문만은 아닌 것 같다. 실제로 C 언어의 창시자인 Brian Kernighan 할아버지도 그런 의견을 피력했던 적이 있었기 때문이다.

때때로 나는 C 대신 C++을 사용해서 프로그램을 작성합니다. 제가 생각하기에 C++은 모든 기능을 내장하기 때문이라고 하더라도 매우 큰 언어입니다. 어떤 크기의 C 프로그램을 작성하든지 저는 75, 80, 90% 정도의 언어 기능을 사용합니다. 다시 말하면 어떤 종류의 프로그램이든 간에 대부분의 C 언어 기능이 유용하다는 점 입니다. 이와는 대조적으로 제가 C++을 사용한다면 언어의 10% 정도도 사용하지 못합니다. 그리고 나머지 90%는 제가 이해하고 있다고 생각하지 않습니다. 이러한 점들에서 전 C++은 너무 크다고 말합니다. 그러나 C++은 큰 프로그램을 작성하는데 필요한 많은 기능들을 제공합니다. 객체를 만드는 것이 가능하며, 내부적인 정보의 표현을 보호할 수 있습니다. 그래서 결국 내부를 보지 못하게 만드는 훌륭한 외관을 표현할 수 있습니다. C++은 제가 생각하기에 아주 방대한 양의 유용한 메카니즘을 가지고 있습니다. 그리고 그것은 C 언어가 당신에게 주지 못하는 점 입니다.

– Brian Kernighan, 2000

기존의 C++이란 언어 자체도 방대한 기능으로 무장한 언어였지만 모던 C++로 불리는 C++11과 C++13, C++14로 불리며 지금 계속 개정 작업이 진행되고 있는 신규 표준은 훨씬 더 방대한 내용을 담고 있다. 상당히 최신 기술들이기 때문에 아직까지도 골수 C++ 프로그래머들도 이러한 기능을 잘 모르는 경우가 많다. 기능을 지원하는 컴파일러들이 제한적이기 때문에 실용성이 없다고 주장하는 입장도 많이 있다. boost는 이런 간극을 메워줄 수 있는 주요한 라이브러리다. 표준에 채택된 기능을 상당수 포함하고 있고, 추후 표준에도 적극적으로 라이브러리 기능들이 제안되고 있기 때문이다. 더불어 C++11을 지원하는 새로운 컴파일러를 사용하지 않더라도 boost를 사용하면 새로운 표준 라이브러리 기능들을 사용할 수 있기 때문에 더욱 효과적이다.

왜 boost인가?
boost의 주요 기능을 소개하기에 앞서서 왜 이런 복잡한 형태의 라이브러리를 사용해야 하는지에 대해서 먼저 알아보는 것이 중요한 것 같다. 왜냐하면 인식이 조금씩 바뀌고 있지만 여전히 boost와 같은 형태의 복잡하고 거대한 라이브러리를 사용하는 것에 대해서 회의적인 프로그래머들이 상당수 있기 때문이기도 하고, 무엇을 공부함에 있어서 그 필요성을 절감하고 공부하는 것과 단지 다른 사람들이 쓴다고 하니까 배우는 것에는 큰 차이가 있기 때문이기도 하다.

반대하는 쪽 주장의 면면을 살펴보면 이렇다. 1) 버그가 있다. 2) 너무 복잡하다. 3) 내가 만드는 걸 선호한다.

이 중에서 버그 이야기를 먼저 해보자. 결론만 먼저 말하면 사실 이런 주장을 하는 프로그래머치고 boost의 버그를 정확하고 명백하게 보여주는 프로그래머는 없었다. 대부분 그들이 도입하면서 겪었던 일부 문제들을 가지고 boost의 버그로 말하는 경향이 있는데 여기에는 boost 라이브러리의 사용법을 명확하게 몰라서 생긴 문제가 거의 대다수였다. 즉, 돌려 말하면 윈도우 API를 잘못 사용해서 잘못된 결과를 얻어놓고서는 윈도우 API의 버그라고 말하는 경우와 동일한 경우가 대부분이라는 점이다. 백 번 양보해서 제대로 된 버그를 발견했다고 치더라도 그걸 고쳐서 오픈소스 커뮤니티에 기여하면 된다. boost와 같은 라이브러리의 경우 전세계 수많은 개발자들이 지켜보고 있기 때문에 그 어떤 프로젝트보다도 결함이 신속하고 빠르게 고쳐진다. 적어도 버그 문제에 있어서는 직접 작성하는 것보다 훨씬 고품질의 결과를 얻을 수 있다는 점은 인정하자.

너무 복잡하다. 맞다. boost 라이브러리는 영문 정식 명칭이 library가 아닌 libraries로 돼 있을 만큼 정말 엄청나게 다양한 라이브러리의 묶음이다. 걔 중에는 유용한 것들도 있지만 당연히 사용하는 쪽 입장에서는 불필요한 부분도 많이 있다. 하지만 이는 다시 생각해보면 복잡해서 잘 모르는 부분은 사용하지 않으면 그만이다. boost를 사용한다고 해서 거기 있는 모든 라이브러리를 몽땅 써야 하는 것은 아니다. 더욱이 boost의 경우에 템플릿 기반의 라이브러리라 대다수 코드들은 헤더를 포함하지 않으면 그 어떤 코드도 우리 프로젝트에 영향을 미치지 않는다.

끝으로 꼭 직접 만든 것만 믿는 프로그래머들이 있다. 하지만 이건 정말이지 바보 같은 생각이다. 왜냐하면 조금만 시각을 달리하면 직접 만든 건 결국 하나도 없기 때문에다. 심지어 C++이란 언어 자체도 직접 만든 것은 아니다. 컴파일러도 마찬가지다. 그러니 직접 만든 것만 쓰겠다는 생각은 자신의 시야를 너무 좁은 곳에 두는 관점이라는 걸 명심하자.

자 그럼 이제 반박을 떠나서 boost와 같은 외부 라이브러리 사용의 긍정적인 측면을 살펴보도록 하자. 버그의 정반대 측면인 안정성을 들 수 있다. boost와 같은 검증된 라이브러리는 수많은 프로그램에서 사용되고, 전세계에 분포한 다양한 개발자들이 프로젝트에 참여하고 있다. 보는 눈이 많은 만큼 훨씬 더 안정적일 수 밖에 없다. 혹여 사용하다가 결정적인 버그를 발견했다손 치더라도 오픈 소스이기 때문에 수정해서 사용할 수 있고, 커뮤니티에 기여하는 경우에는 다음부터는 유사한 문제를 겪는 프로그래머가 없도록 도와 줄 수도 있다.

다음은 멀티 플랫폼이다. 요즘 컴퓨팅 환경의 트렌드를 한 마디로 요약하자면 멀티 플랫폼이다. 컴퓨터가 들어가지 않은 디바이스를 찾기 힘들 지경이다. 자동차, 세탁기, 냉장고, TV, 핸드폰, 손목시계, 안경 등 우리가 상상할 수 있는 모든 곳에는 컴퓨터가 탑재되고 있다. 그만큼 프로그래머들은 고달픈 시대다. 한가지 플랫폼만 했어도 충분했는데 이제는 이쪽 저쪽을 다 신경 써야 하기 때문이다. 이럴 때 가장 좋은 것이 이식성이 있는 소스 코드다. boost의 경우 다양한 환경에 대한 추상화 계층을 사용하기 때문에 boost::asio와 같은 비동기 입출력 라이브러리를 사용한다면 우리는 Windows, BSD, Linux 등과 같은 플랫폼에 상관 없이 그 플랫폼에 얼추 최적화된 구현을 바로 사용할 수 있다.

끝으로 가장 결정적인 이유는 좋은 라이브러리를 써봐야 좋은 라이브러리를 만들 수 있기 때문이다. 컴퓨터 공학을 가르치는 대학의 경우 대부분 소스 코드 베끼기가 실력 향상에 좋지 않다는 이유로 소스 코드 보는 행위를 장려하지 않고 있다. 안타깝게도 이런 현상 때문에 제대로 된 코드를 쓸 줄 아는 프로그래머가 그렇게도 드문 것 같다. 다른 사람이 만든 소스 코드, 또는 선배 프로그래머들이 만든 소스 코드를 봐야지 아 이런 식으로 짜는 구나를 알 수 있다. 파서를 strstr로 짜고 있는 프로그래머, 함수 포인터를 몰라서 switch/case를 일일이 열거하고 있는 프로그래머를 보면 정말이지 어디서부터 이야기를 해야 할지 답이 안 나오는 지경이다. 직접 만들더라도 많은 사람들이 사용하는 좋은 구조에 대해서는 알아두는 것이 도움이 된다는 점을 꼭 기억하자.

shared_ptr
아마도 boost 라이브러리를 접했던 프로그래머의 절반 이상이 shared_ptr 이라는 스마트 포인터 하나를 사용하기 위해서 접했다고 해도 과언이 아닐 만큼 boost 보다 더 유명한 클래스다. 이제는 C++11에 포함돼 있기 때문에 std::shared_ptr 형태로 사용할 수 있다. 하지만 아직 C++11을 지원하는 컴파일러를 사용할 수 없는 환경이라면 boost는 여전히 강력한 대안이다.

C++11 이전의 표준에 포함됐던 유일한 스마트 포인터 클래스는 auto_ptr 클래스였다. auto_ptr의 경우 소유권 이전에 대해서 다소 난해한 정책을 가지고 있어서 스마트 포인터를 처음 접하는 프로그래머에게 좋지 않은 인상을 남기는 허들 역할을 톡톡히 했다. <리스트 1>에는 전형적인 auto_ptr의 잘못된 사용을 보여주고 있다. auto_ptr의 경우에는 복사 시에 소유권이 이전되는 데 이게 상황에 따라서 상당히 미묘한 많은 문제를 일으킨다. 더욱이 이런 특징 때문에 결정적으로 STL 컨테이너와는 전혀 연동을 할 수 없기 때문에 반쪽 짜리 스마트 포인터라는 오명을 가지기도 했었다.

리스트 1 auto_ptr의 소유권 이전 문제를 보여주는 전형적인 예제

view plaincopy to clipboardprint?
  1. #include "memory"  
  2. #include "iostream"  
  3.   
  4. using namespace std;  
  5.   
  6. class Value  
  7. {  
  8. public:  
  9.     int val_;  
  10.     Value()  
  11.     {  
  12.         cout << "Value ctor" << endl;  
  13.     }  
  14.   
  15.     ~Value()  
  16.     {  
  17.         cout << "Value dtor" << endl;  
  18.     }  
  19. };  
  20.   
  21. void fn(auto_ptr<Value> param)  
  22. {  
  23.     param->val_ += 1;  
  24. }  
  25.   
  26. int _tmain(int argc, _TCHAR* argv[])  
  27. {  
  28.     auto_ptr<Value> pa(new Value);  
  29.     pa->val_ = 3;  
  30.     cout << "pa val_ = " << pa->val_ << endl;  
  31.     cout << "fn 호출 시작" << endl;  
  32.     fn(pa);  
  33.     cout << "fn 호출 끝" << endl;  
  34.     cout << "pa val_ = " << pa->val_ << endl;  
  35.       
  36.     return 0;  
  37. }  

auto_ptr의 소유권 이전 문제를 개선한 것이 boost의 shared_ptr이다. shared_ptr은 내부적으로 레퍼런스 카운팅을 사용하기 때문에 복사하더라도 해당 객체를 참조하는 포인터가 모두 사라지는 경우에만 실질적으로 객체 파괴 작업이 일어난다. 복사 시에 레퍼런스 카운트를 증가시키고, 파괴 시에는 레퍼런스 카운트를 감소시켜 0이 되는 경우에 실제로 객체를 파괴하도록 만들어져 있기 때문이다. 이런 장점 때문에 auto_ptr과 달리 STL 컨테이너에 넣어도 안전하다.

shared_ptr은 레퍼런스 카운팅이라는 기본적인 장점에 추가적으로 커스텀 파괴자를 지원하기 때문에 스마트 포인터의 활용도를 한층 더 높일 수 있다는 장점도 있다. <리스트 2>에는 세 개의 파일 리소스를 사용해서 처리를 하는 일반적인 루틴을 보여주고 있다. 이 경우에 사용하는 리소스가 추가될 때마다 인덴트 깊이가 깊어져서 코드가 보기 힘들어지는 경향이 있다. 실제로 스마트 포인터가 없는 COM 코드에서는 이러한 코드가 일반적이다.

<리스트 2>의 코드를 가독성이 좋도록 개선한 버전이 <리스트 3>에 나와 있다. 변경된 코드에서는 실패 시에 바로 함수 실행을 리턴하도록 만들었다. 하지만 이렇게 할 때에도 매 실패 시점마다 프로그래머가 현재 획득한 리소스와 해제해야 할 리소스 목록을 알고 있어야 한다는 단점이 있다.

이런 코드의 경우 가장 깔끔한 해결 방법은 기존의 레거시 파일 처리 루틴을 사용하지 않고 RAII(Resource Acquisition Is Initialization) 형태로 설계된 파일 클래스를 사용하는 것이 정답이다. 하지만 현실 세계에 존재하는 수많은 레거시 코드를 일일이 그런 형태로 래핑하는 것은 너무 이상적인 이야기일 수 밖에 없다. 이런 경우에 중간 단계의 절충안으로 shared_ptr을 사용할 수 있다. <리스트 4>에는 shared_ptr의 커스텀 파괴자를 사용해서 자원을 관리하는 코드가 나와 있다. shared_ptr이 파괴될 때 fclose가 호출되기 때문에 파일 포인터 자원이 자동적으로 소거된다. 이 코드에서 프로그래머는 자원 생성 시에만 파괴 루틴을 등록하고 이후에는 해당 자원에 대해서는 신경 쓰지 않아도 되기 때문에 실수할 가능성이 줄어들고 그만큼 자원 관련 버그도 줄여준다. 예외 발생 시에도 자원을 안전하게 파괴할 수 있다는 것은 추가적인 장점이다.

리스트 2 파일 포인터 자원 3개를 사용하는 일반적인 코드

view plaincopy to clipboardprint?
  1. void  
  2. MergeFile(LPCSTR dst, LPCSTR src1, LPCSTR src2)  
  3. {  
  4.     FILE *dfp;  
  5.     FILE *sfp1;  
  6.     FILE *sfp2;  
  7.   
  8.     dfp = fopen(dst, "wb");  
  9.     if(dfp)  
  10.     {  
  11.         sfp1 = fopen(src1, "rb");  
  12.         if(sfp1)  
  13.         {  
  14.             sfp2 = fopen(src2, "rb");  
  15.             if(sfp2)  
  16.             {  
  17.                 // 실제 처리할 작업  
  18.   
  19.                 fclose(sfp2);  
  20.             }  
  21.   
  22.             fclose(sfp1);  
  23.         }  
  24.   
  25.         fclose(dfp);  
  26.     }  
  27. }  

리스트 3 실패시 리턴 방식을 사용한 코드

view plaincopy to clipboardprint?
  1. void  
  2. MergeFile2(LPCSTR dst, LPCSTR src1, LPCSTR src2)  
  3. {  
  4.     FILE *dfp;  
  5.     FILE *sfp1;  
  6.     FILE *sfp2;  
  7.   
  8.     dfp = fopen(dst, "wb");  
  9.     if(!dfp)  
  10.         return;  
  11.   
  12.     sfp1 = fopen(src1, "rb");  
  13.     if(!sfp1)  
  14.     {  
  15.         fclose(dfp);  
  16.         return;  
  17.     }  
  18.   
  19.     sfp2 = fopen(src2, "rb");  
  20.     if(!sfp2)  
  21.     {  
  22.         fclose(sfp1);  
  23.         fclose(dfp);  
  24.         return;  
  25.     }  
  26.   
  27.     // 실제 처리할 작업  
  28.   
  29.     fclose(sfp2);  
  30.     fclose(sfp1);  
  31.     fclose(dfp);  
  32. }  

리스트 4 shared_ptr을 사용하여 자원 관리를 하는 코드

view plaincopy to clipboardprint?
  1. #include "boost/shared_ptr.hpp"  
  2.   
  3. typedef boost::shared_pt<void> vsptr;  
  4.   
  5. void  
  6. MergeFile3(LPCSTR dst, LPCSTR src1, LPCSTR src2)  
  7. {  
  8.     FILE *dfp;  
  9.     FILE *sfp1;  
  10.     FILE *sfp2;  
  11.   
  12.     dfp = fopen(dst, "wb");  
  13.     if(!dfp)  
  14.         return;  
  15.   
  16.     vsptr dfp_closer(dfp, fclose);  
  17.   
  18.     sfp1 = fopen(src1, "rb");  
  19.     if(!sfp1)  
  20.         return;  
  21.   
  22.     vsptr sfp1_closer(sfp1, fclose);  
  23.   
  24.     sfp2 = fopen(src2, "rb");  
  25.     if(!sfp2)  
  26.         return;  
  27.   
  28.     vsptr sfp2_closer(sfp2, fclose);  
  29.   
  30.     // 실제 처리할 작업  
  31.   
  32. }  

bind
boost에서 shared_ptr 다음으로 유명한 것을 들자면 단연코 bind가 될 것이다. 유연하게 설계된 클래스가 얼마나 효율적으로 기존 시스템과 마찰 없이 잘 결합될 수 있는지를 단적으로 보여주는 클래스라고 할 수 있다.

bind은 기본적으로 함수 호출 객체를 만들어주는 역할을 한다. <리스트 5>에는 기본적인 사용 방법이 나와 있다. fn이라는 기존 함수에 a, b, c, d, e 파라미터로 1, 2, 3, 4, 5를 전달해서 호출하는 함수 객체를 만드는 것을 보여주고 있다. 해당 함수 객체를 호출하면 결과 값은 15가 출력된다.

<리스트 6>에는 플레이스홀더를 사용해서 bind 객체를 생성하는 것을 보여준다. _1로 표시된 자리에 해당 함수 객체를 호출할 첫 번째 파라미터가 들어간다는 의미다. 따라서 이 함수 객체를 호출할 때에는 _1 자리에 들어갈 파라미터를 넣어주어야 한다. 예제에서는 10을 넣어주었다. 따라서 이 객체는 최종적으로 fn(1, 2, 10, 4, 5)를 호출하는 결과를 보여준다.

<리스트 7>에는 플레이스홀더를 두 개 사용한 예제가 나와 있다. _1에는 첫번째 파라미터가, _2에는 두번째 파라미터가 들어간다. 따라서 이 함수 객체를 사용할 때에는 2개의 파라미터를 넣어주어야 정상적으로 호출된다. 예제 코드의 bind는 최종적으로 fn(1, 11, 10, 4, 5)를 호출하는 것과 동일한 기능을 한다.

리스트 5 기본적인 bind 예제

view plaincopy to clipboardprint?
  1. #include "boost/bind.hpp"  
  2.   
  3. int fn(int a, int b, int c, int d, int e)  
  4. {  
  5.     printf("a = %d, b = %d, c = %d, d = %d, e = %d\n", a, b, c, d , e);  
  6.     return a + b + c + d + e;  
  7. }  
  8.   
  9. int main()  
  10. {  
  11.     printf("%d\n", boost::bind(fn, 1, 2, 3, 4, 5)());  
  12.     return 0;  
  13. }  

리스트 6 bind 플레이스홀더 사용 예제 1

view plaincopy to clipboardprint?
  1. int main()  
  2. {  
  3.     printf("%d\n", boost::bind(fn, 1, 2, _1, 4, 5)(10));  
  4.     return 0;  
  5. }  

리스트 7 bind 플레이스홀더 사용 예제 2

view plaincopy to clipboardprint?
  1. int main()  
  2. {  
  3.     printf("%d\n", boost::bind(fn, 1, _2, _1, 4, 5)(10, 11));  
  4.     return 0;  
  5. }  

boost::bind의 경우 클래스 멤버 함수에도 동일하게 적용할 수 있다. <리스트 8>에는 멤버 함수에 bind를 적용한 코드가 나와 있다. 이 코드에 나와 있는 두 bind 호출은 모두 최종적으로 bob.Say(“Hello”)를 호출하는 역할을 한다. boost::bind는 파라미터를 지정하는 경우 값을 복사하는 형태로 동작하기 때문에 내부 동작을 보여주기 위해서 생성자, 복사 생성자, 소멸자에 각각 printf를 추가해 놓았다.. 출력된 내용을 살펴보면 첫 번째 bind의 경우에는 지속적으로 객체가 복사되고 있음을 확인할 수 있다. 이를 제거하기 위해서는 boost::ref를 사용하면 된다. 첫 번째 bind 코드를 boost::bind(&Man::Say, boost::ref(bob), “Hello”)()와 같이 고쳐서 테스트를 해보면 값이 복사되지 않는 것을 확인할 수 있다.

리스트 8 멤버 함수에 bind 적용 예제

view plaincopy to clipboardprint?
  1. #include "stdio.h"  
  2. #include "boost/bind.hpp"  
  3.   
  4. class Man  
  5. {  
  6. public:  
  7.     std::string name_;  
  8.   
  9.     Man(const char *name)  
  10.     {  
  11.         name_ = name;  
  12.         printf("%s ctor\n", name_.c_str());  
  13.     }  
  14.   
  15.     Man(const Man &r)  
  16.     {  
  17.         name_ = r.name_;  
  18.         printf("%s cctor\n", name_.c_str());  
  19.     }  
  20.   
  21.     ~Man()  
  22.     {  
  23.         printf("%s dtor\n", name_.c_str());  
  24.     }  
  25.   
  26.     void Say(const char *msg)  
  27.     {  
  28.         printf("%s: %s\n", name_.c_str(), msg);  
  29.     }  
  30. };  
  31.   
  32.   
  33. int main()  
  34. {  
  35.     Man bob("Bob");  
  36.     boost::bind(&Man::Say, bob, "Hello")();  
  37.     boost::bind(&Man::Say, _1, "Hello")(bob);  
  38.     return 0;  
  39. }  

bind의 객체가 복사된다는 특징은 shared_ptr과 같은 스마트 포인터와 결합하면 좀 더 유연한 동작을 가능하게 한다. <리스트 9>에는 shared_ptr 객체를 참조하는 bind 예제가 나와 있다. bind가 객체를 복사해 두었기 때문에 bob.reset 이후에도 객체가 파괴되지 않고 살아있어서 정상적으로 멤버 함수 호출이 이루어진다. 실질적인 bob 객체는 bind 함수 객체를 담고 있는 function 객체가 사라질 때 같이 소멸된다.

<리스트 9>의 코드에서 객체를 복사하지 않고 boost::ref를 사용해서 직접 참조했다면 bob.reset 시점에 객체가 사라지고 이후 호출되는 fn(“Hello”)는 사라진 객체를 참조하게 되기 때문에 크래시가 발생하는 문제점이 생긴다.

리스트 9 shared_ptr 객체의 멤버 함수를 참조하는 bind 예제

view plaincopy to clipboardprint?
  1. #include "stdio.h"  
  2. #include "boost/shared_ptr.hpp"  
  3. #include "boost/bind.hpp"  
  4. #include "boost/function.hpp"  
  5.   
  6. using namespace boost;  
  7.   
  8. int main()  
  9. {  
  10.     shared_ptr<Man> bob(new Man("Bob"));  
  11.     function< void (const char *) > fn =bind(&Man::Say, bob, _1);  
  12.     bob.reset();  
  13.     fn("Hello");  
  14.     return 0;  
  15. }  

<리스트 9>의 코드에도 잠깐 등장하지만 bind 결과 함수 객체를 저장해놓고 함수 포인터와 같이 지속적으로 사용하기 위해서는 boost::function 클래스를 사용하면 된다. function 클래스의 템플릿 파라미터로 함수 원형을 넣어주면 된다. <리스트 9>의 예제 코드의 fn은 반환값이 없으며(void), 파라미터로 문자열을(const char *) 가지는 함수 객체를 만들겠다는 것을 의미한다.

C++에 익숙하지 않은 프로그래머의 경우에는 여기까지만 이야기하면 이걸 도대체 어디에다 사용하겠다는 건지 의아해 하는 경우가 많다. 그런 경우라면 <리스트 10>에 나와 있는 것과 같은 코드가 도움이 된다. 예제 코드에서는 벡터 멤버를 출력하기 위해서 이터레이터를 만들고 printf를 사용해서 벡터 멤버를 출력하고 있다. 하지만 이를 boost::bind를 사용하면 <리스트 11>에 나와 있는 것과 같이 간단하게 한 줄로 해결할 수 있다. 코드가 짧다는 것을 실수할 가능성은 줄어들고, 가독성은 좋아진다는 것을 의미한다.

리스트 10 이터레이터를 사용해서 벡터 멤버를 출력하는 코드

view plaincopy to clipboardprint?
  1. #include "stdio.h"  
  2. #include "vector"  
  3.   
  4. using namespace std;  
  5.   
  6. int main()  
  7. {  
  8.     vector<int> ints;  
  9.     ints.push_back(1);  
  10.     ints.push_back(2);  
  11.     ints.push_back(3);  
  12.     ints.push_back(4);  
  13.   
  14.     vector<int>::iterator it = ints.begin();  
  15.     vector<int>::iterator end = ints.end();  
  16.     while(it != end)  
  17.     {  
  18.         printf("%d\n", *it);  
  19.         ++it;  
  20.     }  
  21.   
  22.     return 0;  
  23. }</int></int>  

리스트 11 boost::bind를 사용해서 벡터 멤버를 출력하는 코드

view plaincopy to clipboardprint?
  1. #include "stdio.h"  
  2. #include "vector"  
  3. #include "algorithm"  
  4. #include "boost/bind.hpp"  
  5.   
  6. using namespace std;  
  7. using namespace boost;  
  8.   
  9. int main()  
  10. {  
  11.     vector<int> ints;  
  12.     ints.push_back(1);  
  13.     ints.push_back(2);  
  14.     ints.push_back(3);  
  15.     ints.push_back(4);  
  16.   
  17.     for_each(ints.begin(), ints.end(), bind<int>(printf, "%d\n", _1));  
  18.   
  19.     return 0;  
  20. }</int>  

thread
멀티스레드란 환경이 이제는 표준적인 프로그래밍 환경으로 자리잡은 것 같다. 하지만 스레드를 플랫폼마다 지원하는 방식의 차이가 있어서 표준적인 방식으로 사용하기에는 아직 문제가 많다. 이런 경우에는 boost::thread를 사용하면 손쉽게 멀티 플랫폼을 지원하는 스레드 코드를 작성할 수 있다.

<리스트 12>에는 간단한 스레드 작성 코드가 나와 있다. 기존의 윈도우 스레드 구조와 크게 다르지 않은 코드다. boost::thread 생성자로 스레드 함수 포인터와 파라미터를 전달해서 스레드를 생성시키고 있는 것을 볼 수 있다.

윈도우 프로그래밍을 하면 게시판에 올라오는 흔한 질문 중에 하나가 클래스 멤버 함수를 스레드로 구동시키는 방법에 관한 것이다. 컨텍스트에 관한 개념이 없어서 하는 질문인데 기존 윈도우 코드로만 해결하려면 정적 메소드를 만들고 파라미터로 객체 인스턴스를 전달하는 복잡한 과정을 거쳐야 한다. 하지만 boost에서는 boost::bind를 이용하면 손쉽게 클래스 멤버 함수도 스레드로 호출할 수 있다. <리스트 13>에는 boost::bind를 사용해서 클래스 멤버 함수를 스레드로 호출하는 예제가 나와 있다.

리스트 12 boost::thread 예제

view plaincopy to clipboardprint?
  1. #include "stdio.h"  
  2. #include "boost/thread.hpp"  
  3.   
  4. void ThreadProc(const char *msg)  
  5. {  
  6.     printf("%s\n", msg);  
  7. }  
  8.   
  9. int main()  
  10. {  
  11.     boost::thread th1(ThreadProc, "Mark");  
  12.     boost::thread th2(ThreadProc, "Bob");  
  13.   
  14.     th1.join();  
  15.     th2.join();  
  16.     return 0;  
  17. }  

리스트 13 클래스 멤버 함수를 스레드로 호출하는 예제

view plaincopy to clipboardprint?
  1. #include "stdio.h"  
  2. #include "string"  
  3. #include "boost/thread.hpp"  
  4. #include "boost/bind.hpp"  
  5. #include <windows.h>  
  6.   
  7. class Man  
  8. {  
  9. public:  
  10.     std::string name_;  
  11.   
  12.     Man(const char *name)  
  13.     {  
  14.         name_ = name;  
  15.     }  
  16.   
  17.     void Say(const char *msg)  
  18.     {  
  19.         printf("[%d] %s: %s\n", GetCurrentThreadId(), name_.c_str(), msg);  
  20.     }  
  21. };  
  22.   
  23. int main()  
  24. {  
  25.     Man bob("Bob");  
  26.     Man mark("Mark");  
  27.       
  28.     boost::thread th1(boost::bind(&Man::Say, bob, "Hello"));  
  29.     boost::thread th2(boost::bind(&Man::Say, mark, "Bye~"));  
  30.   
  31.     th1.join();  
  32.     th2.join();  
  33.     return 0;  
  34. }</windows.h>  

언어를 넘어서
최근에는 C/C++의 영향력이 급격하게 줄어들고 있는 모양새다. 다양한 요인이 있겠지만 아무래도 첫 번째 요인은 컴퓨팅 파워가 놀랍도록 좋아진 점을 들 수 있을 것 같다. 이제는 더 이상 몇 바이트, 몇 나노초를 계산해가면서 프로그래밍하지 않아도 되는 세상이다. 또 다른 요인으로는 과거와는 다르게 프로그램에 기대하는 요구사항이 폭발적으로 증가했다는 점도 들 수 있다. 고객의 요구사항은 시시각각 변하고, 살아남는 프로그램이 되기 위해서는 그러한 요구사항의 변화를 빠른 속도로 대처해야 한다. 그런 점에서 정적인 언어인 C++은 다소 불리할 수 밖에 없었다.

모던 C++은 이러한 생산성에 관한 취약점을 극복하기 위해서 다양한 기능들을 추가했다. boost 라이브러리 같은 것들을 사용하면 기존 C++ 컴파일러를 사용해서도 제한적이나마 편리한 신규 기능들을 체험해 볼 수 있다. 하지만 이 모든 사실 이전에 반드시 기억해야 할 것은 C++이 항상 우리에게 주어진 문제를 푸는 최선의 도구는 아니라는 점이다.

C++ 언어를 사용하는 프로그래머의 경우 포인터를 다룬다는 점에서 나름 자부심이 많이 있는 편이다. 그래서 그런지 언어에 대한 자부심도 남다른 편인 것 같다. 하지만 모든 문제를 C++로 풀 필요는 없다. 주어진 문제를 해결하는데 적합한 도구를 사용하면 된다. C++로도 풀 수 있는 것이지 다른 대안 언어만큼 쉽게 풀 수 있는 것은 절대로 아니다. 그러니 C++ 언어의 새로운 기능을 꾸준히 탐색하는 것만큼이나 C++이란 언어의 도그마에 빠지지 않도록 각별히 주의해야 할 것 같다. 세상에 주어진 다양한 문제만큼이나 그 문제들을 독창적으로 해결할 수 있는 언어도 많다. 우리는 단지 적절한 도구를 사용해서 그 문제들을 풀기만 하면 되는 것이다.

boost 기능에 대해서 정리가 잘 된 슬라이드~ 백문이 불여일슬라이드 ㅋ~



Read more: http://www.jiniya.net/wp/archives/11769#ixzz3oQf9laZT

저작자표시 (새창열림)

'개발이야기' 카테고리의 다른 글

프레임워크란?  (0) 2016.07.26
프로그래밍에서 SIDE EFFECT 란?  (0) 2015.10.14
개인 프로젝트를 위한 프로젝트 매니지 먼트와 오픈소스  (0) 2013.05.07
윈도우 서버 환경에서, 최대 생성 가능한 소켓(socket) 연결 수는 얼마일까?  (0) 2013.04.16
Posted by hyeonk

svn 능력자를 위한 git 개념 가이드

reference 2015. 8. 20. 14:46


svn 능력자를 위한 git 개념 가이드


출처: http://www.slideshare.net/einsub/svn-git-17386752

저작자표시 (새창열림)

'reference' 카테고리의 다른 글

인생과 일 모두에서 성공하기 위해 반드시 일아야 할 것들  (0) 2014.08.17
Posted by hyeonk

[강좌/메시지] WM_NOTIFY 메시지에 대해서...(ON_NOTIFY) - 2편

mfc & winAPI 2015. 8. 19. 13:57

출처 : http://www.tipssoft.com/bulletin/tb.php/FAQ/65


4. MFC 프로그램에서 WM_NOTIFY 메시지 사용하기
 
    일반적인 방식으로 생각한다면 WM_NOTIFY 메시지에 대한 핸들러는 OnNotify 일것입니다. 
    CWnd 클래스를 살펴보시면 아시겠지만 OnNotify라는 멤버함수가 있습니다. 하지만, 우리는
    이 함수를 등록해서 사용하지는 않습니다. 왜냐하면 이 함수로는 모든 통보 메시지가
    전달되기 때문에 OnNotify 함수에서 코드를 작성하면 C++적인 의미가 다소 줄어들수 있으므로
    이 함수를 등록해서 사용하지 않고 개별적인 처리루틴을 사용하도록 권장하고 있습니다.
    ( OnNotify 함수를 등록하고 if문 또는 switch문을 사용해서 각 메시지를 분류하여 사용하기도
      하는데, 이런 형식은 C 방식에서 많이 사용하는 방법입니다. )
 
    MFC에서는 기본적으로 메시지별로 핸들러 함수를 사용하도록 되어있습니다. 따라서 각 
    통보 메시지별로 핸들러를 등록할수 있는데, 메시지 맵에 아래와 같이 등록하시면 됩니다.
 
    ON_NOTIFY( notify_code, control_id, HandlerFunction )
 
    위와 같이 정의하면 control_id를 가진 컨트롤에서 발생된 통보 메시지 중에 세부 코드가
    notify_code (NMHDR 구조체의 code 항목에 저장된 값) 인 경우, HandlerFunction 함수를
    수행하겠다는 뜻이 됩니다.
 
    예를 들어, 리스트 뷰 컨트롤이 있고 키를 눌렀을때 OnMyListViewKeyDown 이라는 함수를
    호출하고 싶다면 아래와 같이 사용하시면 됩니다.
    ( 해당 리스트 뷰 컨트롤의 아이디 :  IDC_MY_LIST_VIEW 로 가정 )
 
    ON_NOTIFY( LVN_KEYDOWN, IDC_MY_LIST_VIEW, OnMyListViewKeyDown )
 
    사실, 리스트 뷰, 트리 뷰 등과 같이 잘 알려진 컨트롤의 통보 메시지는 클래스위저드를
    이용해서 등록가능하기 때문에 위 사항을 직접 입력하실 필요는 없습니다. ^^;; 하지만,
    자신이 새롭게 만든 컨트롤인 경우에는 클래스위저드의 지원을 받을 수 없기 때문에
    직접 메시지맵에 입력하셔야 합니다.
 
    ON_NOTIFY 매크로의 3번째에 입력한 핸들러는 어떻게 구성되는지에 대해서 알아보겠습니다.
    먼저 함수의 원형은 아래와 같습니다.
 
    afx_msg void HandlerFunction( NMHDR * pNotifyStruct, LRESULT * result );
 
    일반적으로 클래스위저드를 사용해서 등록하게 되면 위 코드가 해당 클래스의 헤더파일 내에
    자동으로 추가됩니다. 그리고 아래와 같이 해당 클래스의 소스 파일에는 핸들러 코드가 추가됩니다.
 
    void 해당클래스::HandlerFunction( NMHDR * pNotifyStruct, LRESULT * result )
    {
        // TODO: Add your control notification handler
        //       code here
   
        *pResult = 0;
    }
 
    위에서 예를 든것처럼 리스트 뷰 컨트롤이 있고 키를 눌렀을때 OnMyListViewKeyDown 이라는 함수를
    호출하고 싶다고 선택했다면 아래와 같이 핸들러 루틴이 추가될 것입니다.
    ( 클래스위저드가 자동으로 추가해 줍니다.  )
 
    void 해당클래스::OnMyListViewKeyDown( NMHDR * pNotifyStruct, LRESULT * result )
    {
        LV_KEYDOWN* pLVKeyDow = (LV_KEYDOWN*)pNotifyStruct;
        // TODO: Add your control notification handler
        //       code here
   
        *pResult = 0;
    }
 
    위 코드에서 보시면 알수 있듯이 리스트 뷰 컨트롤의 LVN_KEYDOWN 통보 메시지는 NMHDR 구조체를
    확장해서 LV_KEYDOWN 이라는 구조체를 사용하고 있기 때문에 클래스위저드가 자동으로 형변환까지
    코드를 생성시켜 줍니다. 만약, 이 강좌의 1편에서처럼 자신이 만든 클래스에 통보 메시지를 보내는
    코드를 보냈고 해당 통보 메시지가 MyNotifyData 라는 구조체를 사용했다면 형변환을 아래처럼 직접
    하시면 됩니다.
 
    MyNotifyData *p_my_data = (MyNotifyData *)pNotifyStruct;
 
    당연한 이야기겠지만 통보 메시지 데이터 자체가 NMHDR을 이용한다면 형변환 없이 pNotifyStruct 변수를
    바로 사용하시면 됩니다.
 

5. 동일한 통보 메시지를 일괄 처리하기
 
    자신이 하나의 대화상자에 리스트 뷰 컨트롤을 3개를 만들고 각 아이디를 IDC_MY_LIST_VIEW1, 
    IDC_MY_LIST_VIEW2, IDC_MY_LIST_VIEW3로 부여했다고 합시다. ( 각 컨트롤의 번호는 
    resource.h 파일에서 확인했을 때, 순차적으로 배열되어있다고 가정하겠습니다. ) 대화상자에서
    각 컨트롤의 LVN_KEYDOWN 통보 메시지를 일괄적으로 처리해야 하는 경우, 핸들러를 3개
    등록하지 않고 1개만 등록해서 처리하는 방법에 대해서 알아보도록 하겠습니다.
 
    먼저 메시지 맵에 아래와 같은 메크로를 등록합니다.
 
    ON_NOTIFY_RANGE( notify_code, start_control_id, end_control_id, HandlerFunction )
 
    위 사항을 적용해서 등록한다면 아래와 같습니다.
 
    ON_NOTIFY_RANGE( LVN_KEYDOWN, IDC_MY_LIST_VIEW1, 
                                                           MY_LIST_VIEW3,  OnMyListViewKeyDown)
 
    Range 관련 메크로는 클래스위저등의 지원을 받지 못하기 때문에 직접 입력하셔야 합니다.
    해당 클래스의 헤더파일에 추가되는 OnMyListViewKeyDown 함수의 형식은 ON_NOTIFY 때와
    약간의 차이가 생기는 아래와 같습니다.
 
    afx_msg void HandlerFunction( UINT control_id, NMHDR * pNotifyStruct, LRESULT * result );
 

    위와 같이 control_id라는 인자가 하나 추가되는데, 그 이유는 3개의 컨트롤에서 발생된 
    통보 메시지가 일괄적으로  이 함수를 사용하기 때문에 어떤 컨트롤에서 이 메시지가
    전달되었는지 확인할수 있도록 통보 메시지를 발생시킨 컨트롤의 아이디가 추가된 것입니다.


저작자표시 (새창열림)

'mfc & winAPI' 카테고리의 다른 글

마우스 이벤트 중복 클릭 방지 함수  (0) 2017.05.29
[강좌/메시지] WM_NOTIFY 메시지에 대해서...(ON_NOTIFY) - 1편  (0) 2015.08.19
#import msado15.dll이 소스에서 OS등에 따라 컴파일이 되지 않을 때  (0) 2014.04.11
BOOL PreTranslateMessage(MSG* pMsg)  (0) 2014.03.21
분할 윈도우 중 하나에 접근하는 방법  (0) 2014.02.10
Posted by hyeonk

[강좌/메시지] WM_NOTIFY 메시지에 대해서...(ON_NOTIFY) - 1편

mfc & winAPI 2015. 8. 19. 13:56

출처: http://www.tipssoft.com/bulletin/tb.php/FAQ/63


WM_NOTIFY 메시지는 우리가 프로그램하면서 많이 사용하는 메시지 중에 하나입니다.:namespace prefix = o ns = "urn:schemas-microsoft-com:office:office" />

하지만 그 용법이나 의도를 제대로 이해하지 못하고 사용하는 사람들이 많아서

MSDN의 Technical note 61번의 내용을 근거로 간단한 소개글을 만들었습니다.

 

 

1. 통보(Notify) 메시지의 필요성

 

    WM_NOTIFY 메시지는 일반적으로 자식윈도우(예, 컨트롤-버튼,에디트박스, ...)가 자신에게

    일어난 여러가지 상황을 부모윈도우(예, 대화상자)에게 전달할때 사용합니다.

 

   예를 들어, 대화상자에 버튼을 하나 만들었다고 하면 해당 버튼을 눌렀을때 WM_COMMAND 라는

   메시지가 발생합니다. 이 메시지는 단순한 통보(notify)를 목적으로 구성되었기 때문에

   WPARAM에 BN_CLICKED(HI WORD)와 해당 컨트롤의 ID(LOW WORD)가 저장되고

   LPARAM에 해당 버튼 컨트롤의 핸들이 저장된 상태로 전달됩니다.

 

   위 구성에서 본것처럼 이미 WPARAM, LPARAM을 모두 사용해버렸기 때문에 버튼 컨트롤 입장에서는

   추가적인 정보(마우스 클릭 좌표와 같은 정보)를 전달하고 싶어도 전달할 방법이 없습니다.

 

   컨트롤의 종류가 다양해지고 요구사항이 복잡해짐에 따라서 윈도우즈 시스템은 추가적인 정보를

   포함할수 있는 메시지가 필요하게 되었고 아래와 같은 메시지가 추가되었습니다.

 

   WM_CTLCOLOR, WM_VSCROLL, WM_HSCROLL, WM_DRAWITEM, WM_MEASUREITEM,

   WM_COMPAREITEM, WM_DELETEITEM, WM_CHARTOITEM, WM_VKEYTOITEM, ...

 

 

2. Win32 시스템에서의 통보 메시지에 대하여...

 

    Win32 시스템은 하위 시스템의 호환성을 유지하기 위해 기존 Windows 3.x에서 사용하던 메시지들을

    대부분 수용하고 있습니다. 즉, 위에서 언급한 메시지들은 모두 사용할수 있습니다.

    그리고 프로그램이 복잡해지고 컨트롤이 다양해짐에 따라, 이미 많은 메시지가 있음에도 불구하고,

    메시지 추가에 대한 요구 사항이 늘어나게 되었고 윈도우즈 시스템은 필요할때마다 WM_ 메시지를

    추가해서 문제를 해결하는데 한계가 있다는것 알고 통보 메시지에 대한 새로운 표준을 정했고

    그것이 WM_NOTIFY 메시지로 구체화 되었습니다.  WM_NOTIFY 메시지의 형식은 아래와 같습니다.

 

 

    WM_NOTIFY 메시지의 LPARAM에 사용된 구조체의 형식은 아래와 같습니다.

 

    typedef struct tagNMHDR {

        HWND hwndFrom;

        UINT idFrom;

        UINT code;

    } NMHDR;

 

    NMHDR 구조체를 보시면 아시겠지만 항목이 너무 간단해서 다양한 정보를 수용할만한 구조가

    아닙니다.

    그렇다면 어떻게 컨트롤의 다양한 정보를 이 구조체를 이용해서 전달할수 있을까요?

    그건 LPARAM에 전달되는 정보의 형식을 살펴보면 의문이 풀립니다.

    LPARAM에 저장된 정보는 NMHDR 구조체의 주소입니다. 즉, 구조체의 크기가 한정되지 않다는

    뜻입니다. WM_NOTIFY 메시지는 전달되는 정보의 시작형식을 NMHDR 구조체 형식으로 정한것이지

    꼭 저 구조체를 사용하라는 뜻이 아닙니다.

 

    예를 들어, 자신이 컨트롤을 하나 만들었고 해당 컨트롤이 x, y 좌표정보를 부모에게 통보해야 한다면

    아래와 같이 구조체를 정의하고 사용하시면 됩니다.

 

    typedef struct tagMyNotifyData {

        HWND hwndFrom;    // 변경불가

        UINT idFrom;           // 변경불가

        UINT code;              // 변경불가

        // 자신이 추가하고 싶은 정보를 추가하면 됩니다.

        int x_pos;

        int y_pos;

    } MyNotifyData;

 

   위 구조체를 아래와 같이 선언해도 마찬가지 입니다.

 

    typedef struct tagMyNotifyData {

        NMHDR hdr;   // 변경불가

        // 자신이 추가하고 싶은 정보를 추가하면 됩니다.

        int x_pos;

        int y_pos;

    } MyNotifyData;

 

    WM_NOTIFY 관련 처리기는 LPARAM으로 전달되는 메시지의 시작형식만 NMHDR 구조를 만족하면

    아무런 문제 없이 동작하도록 구성되어있기 때문에, 자신이 정의하는 구조체의 시작 부분만 정확하게

    NMHDR 구조체 형식으로 구성해주시면 됩니다.

 

    대부분의 통보 메시지를 사용하는 컨트롤들은 자신의 정보를 전달하기 위해서 NMHDR 구조체보다

    더 복잡한 형태의 구조체를 사용할 것입니다.  ( 툴팁 컨트롤이 통보하는 메시지 중에 TTN_SHOW,

    TTN_POP 같은 메시지는 특별한 추가 정보가 없어서 NMHDR 구조를 그대로 사용하고 있습니다. )

 

    WM_NOTIFY 메시지는 통보 메시지의 대표 메시지이고, 자신이 전달하고 싶은 통보 메시지 코드는

    NMHDR 구조체의 code 항목에 저장해서 전달하면 된다. 따라서 받는 쪽에서는 해당 통보 메시지가

    어떤 구조체인지는 모르지만 구조체의 앞부분이 NMHDR 구조체와 형식이 같기 때문에 일단 해당

    주소를 NMHDR 구조체로 형변환(casting)을 한 후에 code 항목을 체크하여 어떤 통보 메시지인지를

    구분하고 다시 정확한 구조체로 형변환(casting)을 해서 사용하게 됩니다.

 

    예를들어, 자신이 위에서 이야기한 MyNotifyData 구조체를 사용했다고 가정하고 code에 MN_DATA

    라는 메시지 코드를 넣어서 부모 윈도우로 WM_NOTIFY 형식으로 전달했다면 받는 쪽에서는 아래와

    같이 처리할 것입니다.

 

    // 통보 메시지는 일괄적으로 NM_NOTIFY 로 전달되기 때문에 어떤 통보메시지 인지를 확인해야

    // 한다.

    // 따라서 LPARAM 에 전달된 주소를 NMHDR로 형변환해서 code값을 확인해야 한다.

    NMHDR *p_hdr = (NMHDR *)lParam;

    if( p_hdr->code == MN_DATA ){

        // 전달된 통보 메시지가 MN_DATA인 경우       

        MyNotifyData *p_my_data =  (MyNotifyData *)lParam;

        // p_my_data 를 사용...

    } else {

        // 다른 통보 메시지인지를 체크

    }

 

 

3. WM_NOTIFY를 사용하는 컨트롤에서 공통적으로 사용되는 메시지들에 대하여..

통보 메시지 코드

통보 내용

  NM_CLICK  사용자가  해당 컨트롤에 마우스 왼쪽 버튼을 클릭한 경우 발생
  NM_DBLCLK  사용자가  해당 컨트롤에 마우스 왼쪽 버튼을 더블 클릭한 경우 발생
  NM_RCLICK  사용자가  해당 컨트롤에 마우스 오른쪽 버튼을 클릭한 경우 발생
  NM_RDBLCLK  사용자가  해당 컨트롤에 마우스 오른쪽 버튼을 더블 클릭한 경우 발생
  NM_RETURN
  해당 컨트롤이 포커스를 가지고 있는 상태에서 ENTER 키를 누른
  경우 발생
  NM_SETFOCUS  해당 컨트롤이 입력 포커스를 획득한 경우 발생
  NM_KILLFOCUS  해당 컨트롤이 입력 포커스를 잃어버린 경우 발생
  NM_OUTOFMEMORY    해당 컨트롤이 메모리 부족으로 명령 수행에 실패한 경우 발생

 

 

[ 2 편에서 계속하겠습니다. ]

저작자표시 (새창열림)

'mfc & winAPI' 카테고리의 다른 글

마우스 이벤트 중복 클릭 방지 함수  (0) 2017.05.29
[강좌/메시지] WM_NOTIFY 메시지에 대해서...(ON_NOTIFY) - 2편  (0) 2015.08.19
#import msado15.dll이 소스에서 OS등에 따라 컴파일이 되지 않을 때  (0) 2014.04.11
BOOL PreTranslateMessage(MSG* pMsg)  (0) 2014.03.21
분할 윈도우 중 하나에 접근하는 방법  (0) 2014.02.10
Posted by hyeonk

10년 만에 새로 태어나는 ‘모던 C++’

c & c++ 2014. 11. 4. 23:19

출처: http://www.bloter.net/archives/196746


C++언어가 다시 주목받고 있다. 10여년간 별다른 업데이트 없던 C++가 2011년부터 대대적인 공사에 들어갔기 때문이다. 현재 2014년과 2017년 개발 로드맵이 나온 상태이고, 이를 통칭해 ‘모던 C++’라고 따로 부르고 있다.

C++ 표준화 작업은 1998년에 시작됐다. C++ 표준화 단체에서는 꾸준히 C++에 대한 부족한 부분을 제안하며 보안점을 토의했는데 2011년에 본격적으로 기능을 넣었다. 10년 만에 업데이트를 해서 그런지 최근 트렌드에 맞는 기능들이 대폭 들어갔다. C++ 개발자들도 관심을 보이고 있으며, 관련 세미나도 꾸준히 열리고 있다.

Modern_cpp_04

 ▲6월14일 열린 MS 테크데이즈  C++ 세미나. 약 200명의 개발자가 참여했다. 

C++는 2000년대 초·중반에 특히 사랑받은 언어다. 국내외를 막론하고 게임 분야에서 활발히 사용됐다. 모바일게임 개발 붐이 일어나면서 그 인기가 조금 주춤한 상태다. 김희준 카카오 엔지니어는  “C++는객체 지향 성격을 가지면서, 동시에 하드웨어를 제어하기 좋은  C언어의 장점 모두를 가지고 있다”라며 “지금은 유니티와 같은 다양한 플랫폼이 나왔지만, 당시만 해도 C++를 대체할 만한 언어가 없을 만큼 높은 성능을 냈다”라고 설명했다.

Modern_cpp_05

▲C++로 만드는 프로젝트 종류(출처: MS 테크데이즈 세미나)

C++ 표준화 단체는 언어 뒤에 연도를 붙이는 식으로 새 버전 이름을 정하고 있다. 2011년에 나온 C++는 ‘C++11’, 2014년에 업데이트된 언어는 ‘C++14’이다. 새롭게 변한 C++언어는 람다식, 알밸류(rvalue) 레퍼런스, 비동기 프로그래밍 등을 지원한다. 이러한 기능은 직접 구현하기 어려운 탓에, 언어 차원에서 지원되면 성능 향상에 큰 도움을 준다. 람다식이나 비동기 방식은 최근 자바나 C# 같은 다른 언어에서도 잇달아 지원된 바 있다.

람다식은 알고리즘을 명확하게 하고 표현을 간결하게 하도록 도와준다. 코드량을 줄여주는 데 큰 도움을 주는 셈이다. 예를 들어, 정렬 기능을 이용하고자 할 때 문자 같은 경우 선후 관계를 따로 정의해야 한다. 람다식은 이러한 정렬 기준를 함수로 만들어, 바로 포인터로 보내준다. 김명신 한국MS 수석 기술부장은 “코드 표현이 간결하기로 소문난 파이썬과 비교해도 손색없을 만큼 C++도 짧고 명료하게 사용할 수 있다”라고 설명했다.

Modern_cpp_01

 ▲평균값을 계산하는 코드. 왼쪽이 파이썬으로 만든 코드, 오른쪽은 C++14로 만든 코드(출처:MS 테크데이즈 세미나)

또 다른 기능인 알밸류 레퍼런스는 애플리케이션 구조에 큰 영향을 준다. 과거 C++는 타입 값을 전달할  때 복사본을 만들고 지우는 과정을 반복했다. 이 과정은 시간과 메모리에 영향을 많이 주는데, 알벨류 레퍼런스가 지원되면서 복사하지 않고도 곧바로 옮길 수 있다. 성능이나 속도를 크게 향상시킬 수 있게 된 것이다.

비동기 프로그래밍도 인기 있다. 비동기 프로그래밍은 개발자들 사이에서 최근 가장 많은 관심을 불러일으키는 기능 중 하나다. 개발하기 어렵고 성능에 영향을 많이 주기 때문이다. 동기 호출이란 특정 기능을 요청하면 결과물이 나올 때까지 기다리는 방식이다. 비동기 호출은 결과 값이 나오기 전에 다른 작업을 할 수 있게 도와준다. 그래서 여러 작업을 동시에 할 수 있는 효과를 준다.

그동안 개발자들은 C++를 복잡하고 배우기 힘든 언어라고 생각해 왔다. C++ 표준화 기구도 이에 대해 고민을 많이 한 모습이다. 이런 인식을 바꾸기 위해 라이브러리를 적극적으로 늘리기로 했다. 개발자가 갖다 붙여 사용할 수 있는 예제를 늘리자는 얘기다. 김희준 개발자는 “자바나 C# 같은 경우 라이브러리가 풍부하기 때문에 웹서버 구축이나 채팅 기능을 코드 몇 줄로 바로 넣을 수 있다”라며 “C++개발자는 이를 많은 코드로 구현해야 했지만, 라이브러리가 추가되면서 더 개발이 쉬워질 것”이라고 설명했다.

C++ 표준화 기구는 현재 라이브러리만 지원하는 팀을 따로 두고 있다. MS와 같은 외부 업체들도 C++ API나 라이브러리를 늘려가는 추세다.

Modern_cpp_08

 ▲네트워크 기능을 지원하는 C++ 오픈소스 ‘카사블랑카‘

김명신 MS 수석 기술부장은 “전세계 C++ 프로젝트 통계를 보면, 절반 이상이 4년 이하의 신생 프로젝트”라며 “C++개발자 중 대다수가 20·30대 주류 개발자인 만큼 최근에도 관심을 받고 있다”라고 설명했다.

Modern_cpp_09

 ▲전세계 C++개발자 나이대 분포. (출처: MS 테크데이즈 세미나)


저작자표시 (새창열림)

'c & c++' 카테고리의 다른 글

IsUtf8() - 문자열이 Utf8인지 체크하는 함수  (0) 2013.08.06
안전한 문자열 함수들  (0) 2013.05.03
extern "C" 에 관하여...  (0) 2013.04.16
Posted by hyeonk
이전페이지 다음페이지
블로그 이미지

hello world! hyeonk lab.

by hyeonk

공지사항

    최근...

  • 포스트
  • 댓글
  • 트랙백
  • 더 보기

태그

글 보관함

«   2025/12   »
일 월 화 수 목 금 토
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31

링크

카테고리

전체 (44)
ios (1)
mfc & winAPI (17)
c & c++ (4)
java (1)
eclipse (1)
visual_studio (2)
javascript (3)
asp (1)
oracle (3)
개발이야기 (5)
윈도우 일반 (2)
etc (2)
reference (2)
personal_reference (0)

카운터

Total
Today
Yesterday
방명록 : 관리자 : 글쓰기
hyeonk's Blog is powered by daumkakao
Skin info material T Mark3 by 뭐하라
favicon

hyeonk lab

hello world! hyeonk lab.

  • 태그
  • 링크 추가
  • 방명록

관리자 메뉴

  • 관리자 모드
  • 글쓰기
  • 전체 (44)
    • ios (1)
    • mfc & winAPI (17)
    • c & c++ (4)
    • java (1)
    • eclipse (1)
    • visual_studio (2)
    • javascript (3)
    • asp (1)
    • oracle (3)
    • 개발이야기 (5)
    • 윈도우 일반 (2)
    • etc (2)
    • reference (2)
    • personal_reference (0)

카테고리

PC화면 보기 티스토리 Daum

티스토리툴바