2017년 6월 28일 수요일

[Effective C++] 항목 30 : 인라인 함수는 미주알고주알 따져서 이해해 두자.

인라인 함수를 사용하면 컴파일러가 함수 본문에 대해 문맥별 최적화를 걸기가 용이해집니다.
인라인 함수의 아이디어는 함수 호출문을 그 함수의 본문으로 바꿔치기하자는 것 남발했다가는 코드의 크기가 커질 게 뻔하다.

인라인 함수로 부풀려진 코드는 성능의 걸림돌이 되기 쉽다. 페이징 횟수가 늘어나고, 명령어 캐시 적중률이 떨어질 가능성도 높아진다.

본문 길이가 굉장이 짧은 인라인 함수를 사용하면, 함수 본문에 대해 만들어지는 코드의 크기가 함수 호출분에 대해 만들어지는 코드보다 작아질 수도 있다.(목적 코드의 크기도 작아지며 명령어 캐시 적중률도 높아진다.

inline은 컴파일러에 대해 '요청'하는 것이지, '명령'이 아니다.

클래스 정의 안에 함수를 정의해 넣으면 컴파일러는 그 함수를 인라인 함수 후보로 찍습니다.
(암시적 정의)

함수 앞에 inline을 붙이면 명시적 정의

inline은 컴파일러 선에서 무시할 수 있는 요청이다.
아무리 인라인 함수로 선언되어 있어도 자신이 보기에 복잡한 함수는 절대로 인라인 확장의 대상에 넣지 않는다.
가상함수 호출 같은 것은 절대로 인라인 해주지 않는다.

보기에 확실한 인라인 함수도 '어떻게 호출하느냐'에 따라 인라인이 되기도 하고 안 되기도 한다는 이야기.

inline void f() {...}; // 컴파일러가 반드시 인라인 해주는 함수라고 가정하고

void (*pf)() = f; // pf는 f를 가리키는 함수 포인터.

f(); // 이 호출은 인라인 될 것이다. 평범한 함수 호출이므로

pf(); // 이 호출은 인라인 되지 않는다 함수 포인터를 통해 호출하고 있으므로

어떤 배열의 원소가 객체일 경우가 대표적인 예인데, 배열을 구성하는 객체들을 생성하고 소멸시킬 때 생성자/소멸자의 함수 포인터를 얻어내려면 함수 본문이 반드시 필요합니다.

생성자와 소멸자는 인라인하기에 그리 좋지 않은 함수입니다.

대부분의 디버거가 무척이나 곤란해 하는 비호감 대상이 바로 인라인 함수라는 점

* 함수 인라인은 작고, 자주 호출되는 함수에 대해서만 하는 것으로 묶어둡시다.
이렇게 하면 디버깅 및 라이브러리의 바이너리 업그레이드가 용이해지고, 자칫 생길 수 있는 코드 부풀림 현상이 최소화되며, 프로그램의 속력이 더 빨라질 수 있는 여지가 최고로 많아집니다.

* 함수 템플릿이 대개 헤더 파일에 들어간다는 일반적인 부분만 생각해서 이들을 inline으로 선언하면 안됩니다.


출처: http://kihyun3297.tistory.com/32 [시작해봅니다.]

2016년 5월 16일 월요일

[browser] 하드웨어 가속에 대한 이해와 적용

하드웨어 가속에 대한 이해와 적용

전통적으로 브라우저는 웹 페이지 콘텐츠의 렌더링을 대부분 CPU에 의존했습니다. 하지만 최근에는 휴대용 기기에도 GPU(graphics processing unit)가 기본으로 포함되고 비디오나 3D 그래픽과 같은 콘텐츠의 사용이 늘면서 GPU를 활용해 웹 페이지의 콘텐츠를 렌더링하는 방법을 고민하게 됐습니다. GPU를 렌더링에 활용하는 하드웨어 가속을 사용하면 성능 향상이라는 이점을 얻을 수 있지만 무분별하게 사용하면 오히려 해가 될 수도 있습니다.
이 글은 하드웨어 가속의 기본적인 이론과 하드웨어 가속을 사용하는 방법을 설명하고, 하드웨어 가속을 사용할 때 고려해야 할 사항과 주의 사항을 살펴봅니다. 마지막으로 새로운 CSS 모듈 명세인 will-change 속성으로 더욱 효율적으로 하드웨어 가속을 사용하는 방법을 설명합니다.

하드웨어 가속

하드웨어 가속은 GPU를 활용하기 때문에 GPU 가속이라고도 부른다. 먼저 하드웨어 가속의 기본 이론을 살펴보고 하드웨어 가속을 사용하는 방법을 설명하겠다.

하드웨어 가속이란?

하드웨어 가속을 이해하려면 브라우저가 웹 페이지를 화면에 렌더링하기까지의 과정을 이해해야 한다.
HTML 소스가 로딩되고 파싱되면 브라우저는 태그를 DOM 트리로 구성한다. DOM 트리는 화면에 표현되는 요소와 화면에 표현되지 않는 요소로 이루어져 있다. 예를 들어 <head>나 <script> 등은 화면에 표현되지 않는 DOM 트리의 요소다.
브라우저는 화면에 표현되는 요소를 RenderObject 트리로 구성한다. RenderObject 트리의 노드는 z-index 속성이나 중첩 등을 처리하기 고안된 RenderLayer에 대응된다.
RenderLayer 가운데 실제 화면에 출력돼야 하는 노드는 다시 GraphicsLayer를 생성한다. 최상위(root) 노드나<canvas><video> 등이 GraphicsLayer를 생성하는 RenderLayer다.
하드웨어 가속은 GraphicsLayer 단위로 렌더링된 이미지를 GPU를 이용해 한 장의 이미지로 합성(composition)해서 화면에 출력하는 기술이다.
다음은 DOM 트리와 RenderLayer, GraphicsLayer의 관계를 나타낸 그림이다.
그림 1 DOM 트리와 RenderLayer, GraphicsLayer의 관계(이미지 출처: https://www.chromium.org/developers/design-documents/gpu-accelerated-compositing-in-chrome)
위 그림의 요소를 다시 설명하면 다음과 같다.
  • DOM 트리: HTML 웹 페이지를 파싱한 트리로, HTML 문서의 각 요소를 쉽게 처리(추가, 삭제 등)하기 위하여 브라우저의 엔진이 사용하는 트리다.
  • RenderObject 트리: DOM 트리로부터 만들어지는 트리로, DOM 트리의 노드 가운데 실제 화면에 표현되는 노드만으로 구성된 트리다.
  • RenderLayer: 브라우저의 엔진이 하드웨어 가속 등을 처리하기 위해 사용하는 논리적인 레이어로, 각 RenderObject의 속성에 따라 RenderLayer에 할당된다.
  • GraphicsLayer: 하드웨어 가속 처리를 위한 물리적인 레이어로, 레이어별 RenderObject를 GraphicsLayer 단위로 렌더링한 뒤 최종적으로 GPU를 통해 합성된다.
GPU에 이미지가 업로드되면 이미지는 3차원 그래픽 표면에 다양한 형태로 매핑될 수 있으며, 위치 이동이나 변형 등 다양한 작업을 이미지를 매핑하는 형태로 적용할 수 있다. 그래서 더 적은 비용으로 다양한 변형과 요소의 합성을 실행할 수 있게 된다.
하드웨어 가속은 웹 페이지의 일부 요소가 아닌 모든 요소를 GPU를 통해 합성하는데, 최상위 레이어(root layer) 개념이 있어서 <body> Hi </body>와 같은 단순한 HTML을 처리할 때도 GraphicsLayer가 생성되고 GPU 텍스처(texture)로 업로드되어 합성한 다음 화면에 출력된다.
텍스처는 간단하게 설명하면 메인 메모리(예: RAM)에서 비디오 메모리(예: GPU VRAM)로 이동되는 비트맵 이미지라고 생각하면 된다.
다시 정리하면 브라우저는 다음과 같은 과정을 거쳐 HTML 코드를 우리가 볼 수 있는 모습으로 브라우저에 출력한다.
  1. HTML 코드를 통해 DOM 트리를 구성하고, 화면에 표현되는 요소는 RenderObject 트리로 구성(필요한 경우 RenderLayer로 매핑)한다.
  2. RenderLayer 요소 가운데 GPU에 업로드되는 요소는 다시 GraphicsLayer로 분리되며, 레이어 각각을 독립적인 소프트웨어 비트맵으로 출력한다.
  3. GPU에 텍스처로 업로드한다.
  4. 다양한 레이어를 화면에 표시할 최종 이미지로 함께 합성한다.
합성 과정은 다음 그림과 같이 분리된 레이어가 합쳐져 하나의 완성된 모습으로 출력하는 과정이다.
Photoshop과 같은 이미지 편집 도구를 사용해 본 경험이 있다면, 여러 개로 분리된 레이어로 작업하고 하나의 이미지로 출력하는 모습을 상상하면 이해가 더 쉬울 것이다.
레이어 합성(이미지 출처: https://aerotwist.com/blog/pixels-are-expensive)

가속 대상

RenderLayer에 포함되는 요소 가운데 다음의 조건 중 한 가지에 부합되면 GraphicsLayer로 설정된다.
  • CSS 3D Transform(translate3d, preserve-3d 등)이나 perspective 속성이 적용된 경우
  • <video> 또는 <canvas> 요소
  • CSS3 애니메이션함수나 CSS 필터 함수를 사용하는 경우
  • 자식 요소가 레이어로 구성된 경우
  • z-index 값이 낮은 형제 요소가 레이어로 구성된 경우. 레이어로 구성된 요소의 위에 위치하면 해당 요소도 레이어로 구성된다.

가속 대상 확인 방법

예를 보면서 어떤 요소(또는 영역)가 GPU에 업로드되는지 확인해 보겠다.
다음과 같은 마크업이 있고, 렌더링된 화면은 아래와 같다.
<div class="cds_area">  
    <div class="pchl">...</div>
    <div class="pchl">...</div>
    <div class="pchl">...</div>
    <div class="pchl">...</div>
    <div class="pchl">...</div>
</div>  
그림 2 하드웨어 가속을 적용하기 전 화면
패널을 감싸고 있는 상위 노드에 CSS 3D 속성을 적용해 하드웨어 가속 대상이 되도록 설정한다.
<div class="cds_area" style="transform:translateZ(0);">  

Show composited layer borders 옵션

Chrome 개발자 도구의 Show composited layer borders 옵션은 웹 페이지에 있는 레이어 영역과 레이어가 어떻게 나뉘어 GPU에 업로드되는지 확인할 수 있게 하는 옵션이다.
Chrome의 개발자 도구에서 Rendering 탭을 선택하고 Show composited layer borders를 선택한다.
그림 3 Chrome 개발자 도구의 Show composited layer borders 옵션
화면을 확인하면 파란색의 그리드와 노란색 선이 나타난다.
  • 파란색 그리드는 하나의 큰 레이어를 업로드해야 하는 경우 타일 형태로 나누어 업로드되는 각각의 영역을 의미한다.
  • 노란색 선 영역은 자체 레이어(GraphicsLayer)를 갖는 요소를 의미한다.
위의 화면에서는 페이지에 한 개의 레이어가 있고 레이어는 파란색 테두리로 나뉘어진 타일의 형태로 GPU에 업로드된다는 것을 알 수 있다.

Layers 탭

Chrome 개발자 도구의 Layers 탭에서는 페이지에 구성된 레이어를 확인할 수 있다. 다음 그림에서 요소가 GPU에 업로드됐음을 확인할 수 있다.
그림 4 Chrome 개발자 도구의 Layers 탭
Chrome 개발자 도구에서 Layers 탭이 안 보이면 다음과 같이 설정한다.
1. Chrome의 주소 창에 chrome://flags/#enable-devtools-experiments를 입력해 설정 페이지를 연다.
2. 설정 페이지의 개발자 도구 실험을 사용합니다.에 있는 사용을 클릭한다.
3. Chrome을 다시 시작한 다음 개발자 도구의 설정 창에서 Experiments를 클릭한다.
4. Layers panel을 선택하고 개발자 도구를 닫았다가 다시 열면 Layers 탭을 선택할 수 있다.

하드웨어 가속 사용 시 고려 사항

하드웨어 가속을 사용하면 웹 페이지의 렌더링 속도가 빨라지지만 잘못 사용하면 오히려 렌더링 속도가 느려지거나 브라우저에 문제가 일어날 수 있다. 하드웨어 가속을 사용할 때 주의할 점과 고려할 사항을 알아보겠다.

주의 사항

하드웨어 가속을 사용하면 다양한 성능 향상을 기대할 수 있지만, 그렇다고 모든 요소를 대상으로 적용하면 안 된다. 하드웨어 가속 대상을 지정할 때 다음의 사항을 기억하기 바란다.
  • 무분별한 하드웨어 가속은 오히려 브라우저를 느리게 한다.
  • 요소에 하드웨어 가속 속성이 부여되면 즉시 대상 영역이 GPU에 업로드되며, 이때 업로드되는 영역이 크면 화면이 깜빡이는 현상이 발생될 수 있다.
  • 요소에 하드웨어 가속 속성이 부여되면 레이어로 분리되며, 레이어는 변경되는 내용이 없는 한 요소를 GPU 메모리에 다시 업로드하지 않는다.
  • 하드웨어 가속 속성을 사용한 요소의 내용이 변경되면 GPU 메모리가 갱신되므로 요소의 내용을 미리 변경한 다음 하드웨어 가속 속성을 부여한다.
  • 성능이 낮은 기기에서 하드웨어 가속을 사용하면 오히려 성능 저하를 가져올 수 있다.

적용 시 고려 사항

하드웨어 가속을 사용할 때는 다음과 같은 점을 고려한다.
  1. 하드웨어 가속을 적용하는 요소의 크기는 작을수록 좋고, 요소의 개수는 화면에서 5~6개로 구성하는 것이 좋다.
    특히, 요소의 속성값에 따라 요소의 영역이 커질 수 있기 때문에 주의해서 적용해야 한다. 예를 들어text-indent나 left 같은 속성에 -999em이나 -9999px과 같이 화면 영역을 지나치게 벗어나게 값을 설정하면, 콘텐츠 영역의 크기가 늘어나고 하드웨어 가속에 의해 구성된 레이어도 커지게 돼 불필요한 메모리를 사용하게 된다.
  2. DOM 요소의 내용이 자주 변경되지 않는 영역에 하드웨어 가속을 적용한다.
    내용 변경이 아닌 이동이나 크기 변경이 자주 발생하는 영역에 하드웨어 가속을 적용하고, 이동이나 크기 변경은 transform 속성을 사용한다.
  3. 기기에 따라 선별적으로 하드웨어 가속을 적용한다.
    JMC(Jindo Mobile Component)는 기기가 하드웨어 가속에 적합한 기기인지 확인할 수 있게 useCss3d() 메서드를 제공한다.
// 하드웨어 가속이 적합한 기기면 true 값을 반환
jindo.m.useCss3d();

성능 개선 사례

하드웨어 가속을 잘못 적용해 문제가 일어났던 사례를 살펴보면서 하드웨어 가속을 어떻게 적용해야 효과적인지 알아보겠다.

상위 노드와 하위 노드 모두에 하드웨어 가속을 적용

다음의 사례는 하드웨어 가속을 잘못 사용해 iPhone 6 Plus에서 브라우저가 종료된 사례다.
문제가 일어난 페이지에서는 다음과 같이 상위 노드와 하위 노드에 모두 CSS 3D 속성을 적용했다.
<div class="flick-panel _top" style="... -webkit-transform: traslate3d(100%,0px,0px);">  
    ...
    <div class="wrap program _cardArea">
        <div class="cds_area _infiniteCardArea">
            <div class="pchl" style="...-webkit-transform: traslate3d(0px,0px,0px);">...</div>
            <div class="pchl" style="...-webkit-transform: traslate3d(0px,104px,0px);">...</div>
다음은 Chrome의 개발자 도구에서 확인한 노드 구성이다.
그림 5 상위 노드와 하위 노드에 모두 CSS 3D 속성을 적용한 노드 구성
Chrome 개발자 도구의 Layers 탭으로 확인한 레이어 구성은 다음과 같다.
그림 6 상위 노드와 하위 노드에 CSS 3D 속성을 적용한 페이지의 레이어 구성
각 패널이 레이어로 구성돼 있는 것을 알 수 있다. 제일 첫 번째 요소의 경우에는 레이어의 크기가 10,202 × 104픽셀이고 예상 메모리(Memory estimate)가 4MB다. 이런 상황이라 브라우저가 종료되거나 기기가 꺼졌다 다시 시작하는 현상이 자주 일어났다.
다음은 문제가 되는 부분을 해결하기 위해 상위 노드에만 CSS 3D 속성을 적용한 마크업이다.
<div class="flick-panel _top" style="... -webkit-transform: traslate3d(100%,0px,0px);">  
    ...
    <div class="wrap program _cardArea">
        <div class="cds_area _infiniteCardArea">
            <div class="pchl" style="...-webkit-transform: translate(0px,0px);">...</div>
            <div class="pchl" style="...-webkit-transform: translate(0px,104px);">...</div>
다음은 Chrome의 개발자 도구에서 확인한 노드 구성이다.
그림 7 상위 노드에만 CSS 3D 속성을 적용한 노드 구성
Chrome 개발자 도구의 Layers 탭으로 확인한 레이어 구성은 다음과 같다.
그림 8 상위 노드에만 CSS 3D 속성을 적용한 페이지의 레이어 구성
상위 노드에만 CSS 3D 속성을 적용하면 모든 노드를 레이어로 구성했을 때보다 메모리를 더 적게 사용한다는 것을 확인할 수 있다.

잘못 구성된 레이어

다음은 일부 레이어가 과도하게 큰 영역으로 구성된 사례다.
그림 9 과도하게 큰 영역이 있는 레이어
왼쪽 화면에서 보듯이 첫 번째 레이어의 크기는 10,202 × 104픽셀이고 예상 메모리가 4MB다. 반면 오른쪽 화면에서 보이는 두 번째 레이어의 크기는 414 × 104픽셀이고 예상 메모리는 168KB다.
이 둘의 차이는 왜 발생한 것일까?
첫 번째 레이어의 하위 요소에 적용된 text-indent 속성 때문이다. text-indent 속성이 다음과 같이 과도하게 큰 값으로 설정돼 있다.
.pchl .ic_up {
    ...
    text-indent: -9999px;
    ...
}
다음은 Chrome 개발자 도구로 확인한 첫 번째 레이어의 CSS 속성이다.
그림 10 첫 번째 레이어의 CSS 속성
하드웨어 가속 속성을 적용한 대상 요소의 하위 노드에 과도하고 크게 설정된 text-indent 속성으로 인해 전체 레이어의 크기가 지나치게 커져 브라우저가 종료되는 현상이 일어난 것으로 보인다.
이 속성을 제거하고 다시 확인하면 다음과 같이 레이어의 크기가 비정상적으로 설정되지 않는 것을 확인할 수 있다.
그림 11 속성을 수정한 레이어

효율적인 하드웨어 가속

앞에서 살펴본 것과 같이 하드웨어 가속을 사용하면 렌더링 속도가 빨라지는 이점이 있지만 무분별하게 사용하거나 잘못 사용하면 문제가 일어날 수 있다.
이러한 문제를 해결하기 위해 새로운 CSS 명세인 will-change 속성을 사용하는 것을 고려할 수 있다.
will-change 속성에 관한 더 자세한 내용은 다음 자료를 참고한다.
W3C CSS Will Change Module Level 1
Everything You Need to Know About the CSS will-change Property
Bye Bye Layer Hacks, Hello will-change

will-change 속성

어떤 특정한 요소에 하드웨어 가속을 적용하고 싶을 때 사용한 방법이 레이어 핵(layer hack)이다. 레이어 핵은 하드웨어 가속을 위해 요소에 CSS 3D 속성을 부여해 요소를 GraphicsLayer로 만드는, 일종의 '핵(hack)'이다. 다음과 같이 transform 속성의 값으로 translate3d(0,0,0)나 translateZ(0) 등을 지정한다.
<div style="transform:translateZ(0);"> text </div>  
그러나 레이어를 생성하는 비용이 크기 때문 실행 환경과 상관 없이 무조건 GPU에 텍스처를 업로드하게 하는 CSS 3D 속성 설정은 경우에 따라 심각한 성능 저하를 불러올 수 있다.
will-change 속성(초기에는 will-animate 속성이었다)은 미래에 변경이 발생할 속성에 관해 브라우저에 힌트를 주는 것이라고 생각하면 된다. 미리 힌트를 주면 브라우저가 해당 변경을 위한 작업을 사전에 최적화해 실제 변경이 발생될 때 더 빠르게 업데이트할 수 있고 더 효율적인 방법으로 GPU에 레이어를 생성할 수 있다. 이 속성은 기존의 레이어 핵을 대체하기 위한 목적도 있다.

사용 방법

브라우저가 will-change 속성을 지원하는지는 다음과 같은 스크립트로 확인할 수 있다.
var hintSupport = window.CSS && window.CSS.supports && CSS.supports('will-change', 'transform');  
will-change 속성의 브라우저 지원 범위는 http://caniuse.com/#search=will-change를 참고한다.
빠른 시점(near future)에 변경일 일어날 속성을 지정한다.
.some { will-change : tansform, opacity }
1) will-change 속성의 이름에서 알 수 있듯이 현재 변경이 발생하고 있는 시점이 아닌, 발생할(will) 시점에 속성을 지정해야 한다. 따라서 다음과 같이 지정하는 것은 피해야 한다.
/* 나쁜 예: 변경이 발생되는 시점에 속성을 지정 */
.element:hover {
    will-change: transform;
    transition: transform 2s;
    transform: rotate(30deg) scale(1.5);
}
2) 변경이 완료된 이후에는 속성을 제거한다.
일반적으로 will-change 속성으로 지정되는 변경 작업은 많은 비용과 리소스를 사용하는 작업이 대부분이다. 최적화 이후 브라우저의 일반적인 동작은 최적화를 위해 수행된 작업을 제거하고 다시 원래 상태로 돌아가는 것이다.
그러나 will-change 속성은 이러한 기본적인 브라우저의 동작을 덮어쓰고 최적화된 작업을 일정 시간 동안 내부적으로 유지하기 때문에 불필요하게 오래 유지되지 않도록 작업이 끝난 이후에는 속성을 제거해야 한다.
var el = document.getElementById('element');  
el.addEventListener('mouseenter', hintBrowser);

el.addEventListener('animationEnd', removeHint);

function hintBrowser(event) {  
    event.target.style.willChange = 'transform, opacity';
}

function removeHint(event) {  
    event.target.style.willChange = '';
}
많은 자료에서 will-change 속성을 적용한 다음 꼭 제거하라고 하는데(http://www.sitepoint.com/introduction-css-will-change-property/#always-remove-will-change) 상황에 따라 변경이 완료된 이후 제거하지 않고 유지하는 것이 필요할 때도 있다.
예를 들어 사용자가 미래의 시점에 동일한 액션을 반복해서 수행할 것으로 예상되거나 페이지에서 계속 사용되는 인터랙션 등에서는 속성을 제거하고 다시 설정하는 것보다 제거하지 않고 계속 유지하는 것이 더 나은 성능을 얻을 수 있다.

마치며

이 글을 통해 하드웨어 가속은 무엇이고 어떻게 적용할 수 있는지 알아보고, 잘못 사용하면 심각한 문제가 일어날 수도 있다는 것을 살펴봤다.
프런트엔드 성능은 지속적인 관심을 두고 꾸준히 개선해 나가야 하기 때문에 한번 적용했다고 해서 끝나는 것이 아니다. 조금이라도 더 나은 성능을 얻을 수 있다면 계속 시도하고 적용하려는 노력이 필요하다. 이 글을 통해 많은 분들이 더 나은 성능을 갖는 웹을 만들 수 있기를 바란다.

박재성|네이버 AU개발랩
NAVER에서 프론트엔드 기술 지원 및 기술 리서치 등을 수행하고 있습니다. 2011년 출간된 "자바스크립트 UI 개발과 Jindo 프레임워크"와 2012년 출간된 "자바스크립트 성능이야기"의 공저자이기도 합니다. 신기술에 대한 탐구에 관심이 많으며, 이에 대한 공유를 위해 노력 중입니다.

2016년 4월 21일 목요일

[Effective C++] 항목 29 : 예외 안전성이 확보되는 그날 위해 싸우고 또 싸우자!

(29)      예외 안전성이 확보되는 그날 위해 싸우고 또 싸우자!
GUI 메뉴 구현을 위한 클래스
class PrettyMenu {
public:
           ...
           void changeBackground(std::istream& imgSrc); // 배경그림을 바꾸는 멤버함수
           ...
          
private:
           Mutex mutex; // 이 객체 하나를 위한 뮤텍스
          
           Image* bgImage; // 현재의 배경그림
           int imageChaged; // 배경그림이 바뀐횟수
};

PrettyMenu changeBackground의 구현
void Pretty::changeBackground(std::istream& imgSrc)
{
           lock(&mutex); // 뮤텍스 획득(14)
          
           delete bgImage; // 이전의 배경그림을 없앱니다.
           ++imageChanges; // 그림 변경 횟수를 갱신합니다.
           bgImage = new Image(imgSrc); // 새 배경그림을 깔아 놓습니다.
          
           unlock(&mutex); / 뮤텍스 해제.
}

예외 안전성이라는 측면에서 볼 때이 함수는 이보다 더 나쁠 수는 없다”.일반적으로 예외 안전성을 확보하려면 2가지의 요구사항을 맞추어야 하는데, 이 함수는 어느 요구사항에도 맞지 않는위험천만의 함수입니다.

예외 안전성을 가진 함수라면예외가 발생할 때 이렇게 동작해야 합니다.
l  자원이 새도록 만들지 않습니다.위의 코드는 자원이 샙니다.왜냐하면 “new Image(imgSrc)” 표현식에서 예외를 던지면,
unlock 
함수가 실행되지 않게 되어뮤텍스가 계속 잡힌 상태로 남기 때문입니다.
l  자료구조가 더럽혀지는 것을 허용하지 않습니다.1. “new Image(imgSrc)”가 예외를 던지면 bgImage가 가리키는 객체는 이미 삭제된 후입니다.
2. 
새 그림이 제대로 깔린 게 아닌데도, imageChanges 변수는 이미 증가되었습니다.

자원 누출 문제 해결방법은객체를 써서 자원 관리를 전담케 하는 방법(13)뮤텍스를 적절한 시점에 해제하는 방법을 구현한 Lock 클래스(14)를 
그대로 따라하면 마무리 된다.

void Pretty::changeBackground(std::istream& imgSrc)
{
           Lock m1(&mutex); // 항목(14): 뮤텍스를 대신 획득하고이것이 필요 없어질 시점에 바로 해제해 주는 객체
          
           delete bgImage;
           ++imageChanges;
           bgImage = new Image(imgSrc);
          
           unlock(&mutex); / 뮤텍스 해제.
}
Lock 등의 자원관리 전담 클래스를 쓰면 가장 좋은 점 중 하나는함수의 코드 길이가 짧아진다는 것입니다. Unlock을 호출할 필요가 없습니다.

자료구조 오염 문제.
예외 안전선을 갖춘 함수는 다음 3가지 보장(guarantee) 중 하나를 제공해야 합니다..
l  기본적인 보장(basic guarantee)함수 동작 중에 예외가 발생하면실행중인 프로그램에 관련된 모든 것들을 유효한 상태로 유지하겠다는 보장입니다.어떤 객체나 자료구조도 더럽혀지지 않으며모든 객체의 상태는 내부적으로 일관성을 유지하고 있습니다. (모든 클래스 불변속성이 만족된 상태입니다).하지만 프로그램의 상태가 정확히 어떠한지는 예측이 안될 수도 있습니다.예를 들어, changeBackground 가 동작하다가예외가 발생했을 때 PrettyMenu 객체는 바로 이전의 배경그림을 그대로 계속 그릴 수도 있고아니면 처음부터 마련해 둔 기본 배경그림을 사용할 수도 있을 것입니다.이 부분은 전적으로 함수를 만든 사람에 달려 있지요.하지만 사용자 쪽에서는 어떻게 될지 예측할 수 없습니다 (알아내려면현재의 배경그림이 무엇인지를 알려 주는 다른 멤버 함수를 호출하든지 해야겠지요).
l  강력한 보장(string guarantee): 함수 동작 중에 예외가 발생하면프로그램의 상태를 절대로 변경하지 않겠다는 보장입니다이런 함수를 호출하는 것은 원자적(atomic) 동작이라고 할 수 있습니다호출이 성공하면 (예외가 발생하지 않으면마무리까지 완벽하게 성공하고호출이 실패하면 함수 호출이 없었던 것처럼 프로그램의 상태가 되돌아간다는 면에서 말이죠.
쓰기 편한가의 측면에서 보면 강력한 보장을 제공하는 함수가 기본 보장을 제공하는 함수보다 더 쉽습니다예측할 수 있는 프로그램의 상태가 2개 밖에 안 되기 때문입니다그러니까 함수가 성공적으로 실행을 마친 후의 상태,아니면 함수가 호출될 때의 상태만 존재하는 거죠이와 대조적으로 함수가 기본 보장을 제공하는 경우에는예외 발생 시에 프로그램이 있을 수 있는 상태가 그냥 유효하기만 하면어떤 상태도 될 수 있습니다.
l  예외불가 보장(nothrow guarantee)예외를 절대로 던지지 않겠다는 보장입니다. 약속한 동작은 언제나 끝까지 완수하는 함수라는 뜻이죠기본 제공 타입(int, 포인터 등)에 대한 모든 연산은 예외를 던지지 않게 되어 있습니다 (예외불가 보장이 제공됩니다). 예외에 안전한 코드를 만들기 위한 가장 기본적이며 핵심적인 요소가 아닐까 싶네요.어떤 예외도 던지지 않게끔 예외 지정이 된 함수는예외불가 보장을 제공한다고 생각해도 일견 맞을 것 같지만잘못 생각하신 겁니다.
Int doSomething() throw(); // 비어 있는 예외 지정.
위의 함수 선언이 전하는 메시지는 doSomething이 절대로 예외를 던지지 않겠다는 말이 아닙니다만약 doSomething에서 예외가 발생되면 매우 심각한 에러가 생긴 것으로 판단되므로지정되지 않은 예외가 발생했을 경우에 실행되는 처리자는 unexpected 함수가 호출되어야 한다는 뜻입니다사실 doSomething은 어떤 예외 안전성 보장도 제공하지 않을 수도 있습니다함수 선언문에는(예외 지정이 붙어 있으면 이것도 포함됩니다해당 함수가 맞는지,이식성이 있는지아니면 효율적인지 알려 주는 기능 같은 것이 없습니다예외 안전성 보장을 제공하는지도 당연히 알려 주지 않습니다함수가 어떤 특성을 갖느냐 하는 부분은 구현이 결정하는 것입니다. ‘선언은 그냥 선거공약 같은 거라고요.

앞에서 말했지만예외 안전성을 갖춘 함수는 위의 세 가지 보장 중 하나를 반드시 제공해야 합니다아무 보장도 제공하지 않으면 예외에 안전한 함수가 아닙니다따라서 여러분이 선택해야 하는 것은 어떤 보장을 제공할 것인가이겠습니다.예외 안전성이 없는 재래식(legacy) 코드를 사용해서 작업할 때를 제외하면(이 부분에 대한 이야기는 이번 항목의 뒷부분에서 따로 설명하겠습니다),

위의 3가지 보장 중에 하나를 고르라면 아무래도 실용성이 있는 강력한 보장이 괜찮아 보일 것입니다예외 안정성의 관점에서 보면 예외불가 보장이 가장 훌륭하겠지만예외를 던지는 함수를 호출하지 않고 C++ C 부분으로부터 벗어나오기란 힘들거든요.일단 동적 할당 메모리를 사용하는 쪽(STL 컨테이너가 실제로 그렇습니다)만 보아도요청에 맞는 메모리를 확보할 수 없으면 bad_alloc 예외를 던지도록 구현되어 있지 않습니까(항목49).뭐 할 수 있으면 예외불가 보장을 제공하세요하지만 현실적으로는 대부분의 함수에 있어서 기본적인 보장과 강력한 보장 중 하나를 고르게 됩니다.

changeBackground를 다시 보자이 함수의 경우엔 강력한 보장을 거의 제공하는 것은 그다지 어렵지 않습니다.첫째로, PrettyMenu bgImage 데이터 멤버의 타입을 기본제공 포인터 타입인 Image*에서 자원관리 전담용 포인터(항목13)로 바꿉니다자원 누출을 막는 대책으로 본다면 이렇게 가는 게 딱 맞습니다.사용자에게 강력한 예외 안전성 보장을 제공할 수 있게 만든 것뿐인데 객체(스마트 포인터 등)를 써서 자원을 관리하는 것이 좋은 설계의 첫걸음(항목13)아래 코드에서 자원관리용 객체로 tr1::shared_ptr을 쓸 겁니다. ato_ptr도 있긴 하지만복사될 때의 동작이 더 직관적이라 사용하기가 더 좋거든요.
둘째로, changeBackground 함수 내의 문장을 재배치해서 배경그림이 진짜로 바뀌기 전에는 imageChanges를 증가시키지 않도록 만듭니다어떤 동작이 일어났는지를 나타내는 객체를 프로그램 내에서 쓰는 경우에는해당 동작이 실제로 일어날 때까지 그 객체의 상태를 바꾸지 않는 편이 좋다고 하지요.

class PrettyMenu {
public:
           ...
           std::tr1::shared_ptr<Image> bgImage;
           ...
};
void Pretty::changeBackground(std::istream& imgSrc)
{
           Lock m1(&mutex);
           bgImage.reset(new Image(imgSrc)); // bgImage의 내부 포인터를 “new Image” 표현식의 실행 결과로 바꿔치기합니다.
          
           ++imageChanges;
}

이제는 이전의 배경그림(Image 객체)을 프로그래머가 직접 삭제할 필요가 없게 되었습니다지금은 배경그림이 스마트 포인터의 손에서 관리되고 있기 때문입니다.게다가새로운 배경그림이 제대로 만들어졌을 때만 이전 배경그림의 삭제 작업이 이루어지도록 바뀐 점도 눈에 들어옵니다.다시 말해이제는 tr1::shared_ptr::reset 함수가 호출되려면 이 함수의 매개변수(“new Image(imgScr)”의 결과)가 제대로 생성되어야 한다는 것입니다.
delete 
연산자는 reset 함수 안에 쏙 들어 있기 때문에, reset이 불리지 않는 한 delete도 쓰일 일이 없을 것입니다
.객체(tr1::shared_ptr)를 써서 자원(동적 할당된 Image 객체)을 관리하게 하니까 changeBackground 함수의 길이까지 줄어들었습니다.

매개변수 imgScr, Image 클래스의 생성자가 실행되다가 예외를 일으킬 때그 시점에 입력 스트림의 읽기 표시자가 이동한 채로 남아 있을 가능성이 충분히 있을 테고이 표시자의 이동이 전체 프로그램의 나머지에 영향을 미칠 수 있는 어떤 변화로 작용할 수도 있을 것입니다.따라서 엄밀히 말하면 changeBackground가 제공하는 예외 안전성 보장은 기본적인 보장입니다.

예외 안전성 보장을 제공하는 함수로 거듭나게 만드는 일반적인 설계 전략을 하나 알아보도록 하죠이 전략은 복사 후 – 맞바꾸기(copy-and-swap)’라는 이름으로 알려져 있는데원리적으로 무척 간단합니다어떤 객체를 수정하고 싶으면 그 객체의 사본을 하나 만들어 놓고 그 사본을 수정하는 것입니다이렇게 하면 수정 동작 중에 실행되는 연산에서 예외가 던져지더라도 원본 객체는 바뀌지 않은 채로 남는 거죠필요한 동작이 전부 성공적으로 완료되고 나면 수정된 객체를 원본 객체와 맞바꾸는데이 작업을 예외를 던지지 않는’ 연산 내부에서 수행합니다.

이 전략은 대개 진짜’ 객체의 모든 데이터를 별도의 구현(implementation) 객체에 넣어두고그 구현 객체를 가리키는 포인터를 진짜 객체가 물고 있게 하는 식으로 구현합니다.
‘pimp 
관용구라고들 부르는 이 구현 방법은 항목31에서 
이 방법을 PrettyMenu에 적용한 코드가 다음이다.

struct PMImpl {                            // PMImpl = “PrettyMenuImpl”
           std::tr1::shared_ptr<Image> bgImage; // PMImpl struct로 선언된 데에는 이유가 있습니다아래에서 확인하세요.
           int imageChanges;
};

class PrettyMenu {
           …
private:
           Mutex mutex;
           std::tr1::shared_ptr<PMImpl> pImpl;
};

void PrettyMenu::changeBackground(std::istream& imgScr)
{
           using std::swap; // 항목25.
           Lock m1(&mutex);
           std::tr1::shared_ptr<PMImpl> pNew(new PMImpl(&pImpl)); // 객체의 데이터 부분을 복사합니다.
           pNew->bgImage.reset(new Image(imgScr)); // 사본을 수정합니다.
           ++pNeww->imageChanges;
           Swap(pImpl, pNew); // 새 데이터로 바꿔 넣어 진짜로 배경그림을 바꿉니다.
}

복사--맞바꾸기’ 전략은 객체의 상태를 전부 바꾸거나 혹은 안 바꾸거나(all-or-nothing)’ 방식으로 유지하려는 경우에 아주 그만입니다그러나 함수 전체가 강력한 예외 안전성을 갖도록 보장하지는 않는다는 것이 일반적인 정설입니다.
왜 그럴까요? changeBackground 함수의 전체 흐름을 추상화해 놓은 someFunc()를 한번 살펴봅시다. ‘복사--맞바꾸기’ 수법을 쓰되, f1  f2라는 다른 함수의 호출문이 들어 있는 형태로 말이죠다음과 같은 형태로 나올 겁니다.
void somdFunc()
{
… // 이 함수의 현재 상태에 대해 사본을 만들어 놓습니다.
f1();
f2();
… // 변경된 상태를 바꾸어 넣습니다.
}

f1 혹은 f2 에서 보장하는 예외 안전성이 강력하지 못하면위의 구조로는 someFunc 함수 역시 강력한 예외 안전성을 보장하기 힘들어집니다예를 들어 f1이 기본적인 보장만 제공한다고 가정하면, someFunc 함수에서 강력한 보장을 제공하게 만들려면 (1) f1을 호출하기 전에 프로그램 전체의 상태를 결정하고 (2) f1에서 발생하는 모든 예외를 잡아낸 후에 (3) 원래의 상태로 되돌리는 코드를 작성해야 합니다.

f1  f2 모두가 강력한 예외 안전성을 보장한다고 해도 사실 별로 나아지는 것은 없습니다예를 들어 어차피 f1이 끝까지 실행되고 나면프로그램 상태는 f1에 의해 어떻게든 변해 있을 것이고그 다음에 f2가 실행되다가 예외를 던지면 그 프로그램의 상태는 someFunc가 호출될 때의 상태와 아예 달라져 있을 것이니까요. f2에서 아무것도 바꾸지 않았더라도 말입니다.

여기서 불거지는 문제가 바로 함수의 부수효과(side effect)입니다.자기 자신에만 국한된 것들의 상태를 바꾸며 동작하는 함수의 경우(예를 들어 someFunc는 이 함수의 내부에서만 사용하는 객체의 상태에만 영향을 주고 있죠)에는 강력한 보장을 제공하기가 비교적 수월합니다그렇지만 비지역 데이터에 대해 부수효과를 주는 함수는 이렇게 하기가 무척 까다롭습니다.

강력한 예외 안전성 보장을 제공하게 하고 싶어서 아무리 열을 내더라도 이런 문제 때문에 발목을 잡힐 수 있다는 사실을 알고 계셨으면 좋겠습니다효율 문제도 무시할 수 없습니다. ‘복사--맞바꾸기’ 방법의 요체는 객체의 데이터에 대해 사본을 만들어 놓고 그 사본을 변경한 후에사본과 원본의 바꿔치기 작업을 예외를 던지지 않는 함수 내부에서 하자는 아이디어 입니다때문에 수정하고 싶은 객체를 복사해 둘 공간과 복사에 걸리는 시간을 감수해야 하겠지요이런 부분에 여유가 없거나 왠지 꺼림칙한 분이 분명히 있을 것입니다어쨌든 예외 안전성 보장 중에는 강력한 보장이 가장 좋습니다실용이 확보되는 경우라면 반드시 제공하는 게 맞고요. 그러나 언제나 실용적인 것은 아니랍니다.

l  예외 안전성을 갖춘 함수는 실행 중 예외가 발생되더라도 자원을 누출시키지 않으며 자료구조를 더럽힌 채로 내버려 두지 않습니다이런 함수들이 제공할 수 있는 예외 안전성 보장은 기본적보장강력한보장예외금지보장이 있습니다.
l  강력한 예외 안전성 보장은 복사--맞바꾸기’ 방법을 써서 구현할 수 있지만모든 함수에 대해 강력한 보장이 실용적인 것은 아닙니다.
l  어떤 함수가 제공하는 예외 안전성 보장의 강도는그 함수가 내부적으로 호출하는 함수들이 제공하는 가장 약한 보장을 넘지 않습니다.

[Effective C++] 항목 30 : 인라인 함수는 미주알고주알 따져서 이해해 두자.

인라인 함수를 사용하면 컴파일러가 함수 본문에 대해 문맥별 최적화를 걸기가 용이해집니다. 인라인 함수의 아이디어는  함수 호출문을 그 함수의 본문으로 바꿔치기하자는 것  남발했다가는 코드의 크기가 커질 게 뻔하다. 인라인 함수로 부풀려진 ...