More effective C++
NHN NEXT 장문익
항목9: 리소스 누수는 소멸자로
auto_ptr
auto_ptr로 try-catch 대체
리소스를 얻어내는 생성자와 해제하는 소멸자가 필요한 경우
리소스를 얻어내는 생성자와 해제하는 소멸자
항목10: 생성자에서 리소스 누수 방지
• C++에서는 생성 도중에 예외를 일으킨 객체에 대해서는 마무
리 작업을 해주지 않는다.
• 이런 작업이 필요하다면 프로그래머가 직접 생성자를 설계해야
한다.
• try-catch를 사용하여 생성자를 구현할 수도 있다.
try-catch를 이용한 생성자 예외처리
try-catch를 멤버 초기화 리스트 밖으로
try-catch를 멤버 초기화 리스트 밖으로
auto_ptr로 개선
항목11: 소멸자에서는 예외가 탈출못하게
• 클래스 소멸자가 호출되는 경우는?
• 객체가 통상적인 조건에서 소멸되는 것인데, 통상적인 조건이란
지역변수로 선언된 객체가 유효범위(scope)를 벗어났을때와 객
체가 직접 삭제(delete)될 때
• 예외 처리 매커니즘에 의해 객체가 소멸되는 것인데, 예외 전파
(exception propagation) 과정의 일부분으로 스택 되김가기 진
행될 때
• 즉, 소멸자가 호출되었을 때 예외일수도 아닐 수도 있다.
C++ terminate함수
• 프로그램의 실행을 끝장낸다.
• 지역 객체조차도 소멸되지 않는다.
• 어떤 예외에 대한 처리가 진행되고 있는 동안 또 다른 예외 때
문에 프로그램 흐름이 소멸자 함수를 떠나면, C++ terminate 함
수를 호출하게 된다.
• 이를 고려하여 어떤 상황에서는 소멸자를 작성할 때 예외가 발
생된 상태에 있다고 가정하여야 한다.
terminate 호출의 예
logDestruction에의 예외가 Session 소멸자를 빠져나가지 못하게
logDestruction에의 예외가 Session 소멸자를 빠져나가지 못하게
소멸자에서 발생한 예외가 처리되지 않으면 소멸자는 실행이 끝나지 않는다
항목12: 예외발생의 특이점
예외는 객체의 사본으로서 발생
예외를 전파하는 방법
값에 의한 예외 처리
암시적 변환
catch문에서는 암시적 변환 되지 않는다
예외 타입과 catch문이 받는 타입 일치시키는 타입 변환
• 상속 기반의 변환
상속 기반의 예외 변환
catch() 문에서 허용되는 타입변환
• 타입이 있는 포인터로부터 타입이 없는 포인터로 바꾸는 경우
• 즉, const void* 포인터를 받는 catch 문은 어떤 포인터 타입의
예외이든지 잡아 낼 수 있다.
• catch (const void*) …
매개변수 전달과 예외 전파 사이 차이점3
• catch 문은 등장한 순서에 따라 사용된다.
• 파생 클래스 타입을 받는 catch 문이 준비되어 있다고 해도 순
서가 제대로 되어 있지 않으면, 기본 클래스 타입을 받는 catch
문이 파생 클래스 타입의 예외를 잡아낼 수 있다.
catch문은 파생클래스를 기본클래스 앞으로
항목13: 발생한 예외는 참조자로
• catch 문을 작성할 때에는 예외 객체가 전달되는 방식을 설정해
야 한다.
• 포인터, 값, 참조자에 의한 전달이 가능하다.
포인터로 예외 객체 전달
포인터에 의한 예외전달
• 예외 객체가 힙에 할당된 주소라면 메모리 누수를 막기 위해 포
인터를 삭제해야 한다.
• 그런데 예외 객체가 힙에 할당되지 않는다면?
• 사용자에 따라 다르다.
• 어떤 사용자는 전역 객체나 정적 객체의 주소를 넘길 수 있고,
어떤 사용자는 힙에 할당된 예외 객체의 주소를 넘길 수 있다.
• C++ 기본 제공 표준 예외는 객체에 대한 포인터가 아니라 모두
객체이다. 포인터를 사용하면 이 예외들을 사용할 수 없다.
값에 의한 예외받기(catch-by-value)
• 예외 삭제 고민에서 해방
• C++ 표준 예외와도 잘 맞다.
• 전달되는 예외 객체는 늘 두 번씩 복사되어야 한다.
• 슬라이스 문제(slicing problem): 발생 시에는 파생 클래스의 객
체였다가, 기본 클래스를 받는 catch 문에 들어가면 파생 클래
스 부분에 추가되었던 데이터가 싹둑 잘려 나가는 현상
slicing problem
참조자에 의한 예외받기(catch-by-reference)
• 객체 삭제에 대한 고민이 필요 없다.
• C++ 표준 예외를 처리하는 데에도 무리가 없다.
• 슬라이스 문제도 없고 예외 객체는 한 번만 복사된다.
항목14: 예외지정은 냉철하게 사용
• 예외지정(exception specification): 함수가 발생시킬 예외를 미
리 지정할 수 있는 기능
예외지정 불일치를 피하는 템플릿
예외 지정 안 된 함수를 호출하는 함수에는 예외 지정을 두지 않는다.
시스템이 일으키는 예외를 처리
• “시스템”이 일으킬 가능성이 있는 예외(C++ 표준 예외)
• 이런 예외 중 가장 흔한 것이 bad_alloc
• 이 예외는 메모리 할당에 실패한 operator new와 operator
new[]가 발생시킨다.
예기치 않은 예외를 다른 타입의 예외로 대체
예기치 않은 예외를 다른 타입의 예외로 대체
예외 지정의 고려할 점
• 어떤 예외가 발생될지를 알려준다는 면에서 문서화에 도움이
된다.
• C++는 예외 지정의 위배가 발생하면 기본적으로 프로그램을
일 시 중단시킨다.
• 컴파일러는 예외 지정에 대해 부분적인 일치성 점검만 수행하
고, 프로그래머는 예외 지정의 일치성을 어기기 쉽다.
항목15: 예외처리 비용을 제대로 알자
• 예외 처리 기능을 전혀 쓰지 않았을 때에 비용은
• 어떤 객체가 생성 과정을 완료했는지 체크하는 데에 내부적으
로 사용되는 자료구조에 대한 메모리가 소모된다.
• 이 자료구조를 업데이트하는 데에 필요한 시간이 소모된다.
• 결론적으로 예외 처리 기능을 배제하고 컴파일한 프로그램은
예외 처리를 지원하도록 컴파일한 것에 비해 속도도 빠르고 크
기도 작다.
try 블록으로 생기는 비용
• try 블록이 소스에 들어가기만 하면, 즉 예외를 처리하겠다고 작
정하면 무조건 지불해야할 비용이다.
• 예외 지정 기능에 대해서도 try 블록과 비슷한 양의 코드가 생성
되기 때문에, 예외 지정에 들어가는 try 블록과 비슷하다.
• 예외가 발생되는 경우는 드물기 때문에 예외 발생 시 소모되는
비용은 큰 관심사가 되지 못한다.
• 우선 가능하다면 예외 기능을 지원하지 않도록 컴파일한다.
항목16: 80-20 법칙
• 프로그램 리소스의 80%는 전체 실행 코드의 약 20%만이 사용
한다.
• 실행시간의 80%는 실행 코드의 약 20%만 소모한다.
• 메모리의 80%는 실행 코드의 약 20%만이 사용한다.
• 디스크 접근 회수의 80%는 실행코드의 20%가 접근한 회수다.
• 프로그램 유지보수에 들어가는 수고의 80%는 실행 코드의 20%
에 집중된다.
• 아무 곳이나 골라잡고 효율을 향상시키려고 애쓰지 마라.
프로그램 프로파일러
• program profiler
• 성능 향상을 만들어 낼 수 있는 20%를 판별하기 위해 사용한다.
• 사용자가 관심을 두고 있는 리소스를 직접 측정해주는 도구가
필요하다.
• 수행 성능 문제에 대처하는 최선의 길은 가능한 많은 데이터를
사용해서 소프트웨어를 프로파일링하는 것이다.
항목17: 지연 평가(lazy evaluation)
• 지연(to be lazy): 어떤 일을 하긴 하되 그 일을 하는 코드의 실
행을 피하는 방법
• 지연 평가: 지연 평가를 사용해서 만든 C++ 클래스는 어떤 처
리 결과가 진짜로 필요해질 때가지 그 처리를 미룬다. 어떤 컴퓨
팅 작업을 수행하는 데에 있어서 그 작업 결과가 진짜로 요구되
기 전에는 그것을 하지 않는다.
참조 카운팅(reference counting)
데이터 읽기와 쓰기를 구분하기
지연 방식의 데이터 가져오기(lazy fetching)
lazy fetching
mutable
• const 멤버 함수는 ‘보통의’ 클래스 데이터 멤버를 수정할 수 없
다.
• mutable로 선언하면 “이 데이터는 어떤 멤버 함수에서도 수정
이 가능함“이란 뜻이다.
• const 멤버도 수정할 수 있다.
지연 방식의 표현식 평가(lazy expression evaluation)
수치 계산에 있어서 지연 평가
• 두 값 사이의 의존 관계를 저장해야 한다.
• 값과 의존 관계를 저장하는 자료구조도 필요하다.
• 대입, 복사, 덧셈 같은 연산자에 대한 오버로딩도 필요하다.
• 이런 수고로움이 따르지만 상당량의 수행 시간/가용 메모리 절
약을 해준다는 장점이 있다.
항목18: 예상되는 계산 결과를 미리 준비
• 과도 선행 평가(over-eager evaluation)
• 현재 요구된 것 이외에 더 많은 작업을 미리 해둠으로써 소프트
웨어의 성능을 향상시킨다.
과도 선행 평가 방법이 필요한 경우
이미 계산이 끝났고 다시 사용될 것 같은 값을 캐싱하는 방법
• 데이터베이스에 대한 질의 반복하면 데이터베이스에 부하가 생
긴다.
• 이를 방지하기 위해 이전에 뽑아 낸 데이터를 캐싱해두는 함수
를 만든다.
• 이미 탐색한 데이터는 데이터베이스가 아닌 캐시를 통해 가져
오도록 만든다.
캐싱하는 함수
미리가져오기(prefetching)
• 디스크 컨토롤러는 디스크에서 데이터를 읽을 때, 프로그램 쪽
에서 아주 적은 양만 요구했음에도 불구하고 블록 하나 혹은 섹
터 하나를 왕창 읽는다.
• 조금씩 여러 번 읽는 것보다 한 번에 많이 읽는 쪽이 더 빠르다.
• locality of reference
prefetching이 유용한 경우
prefetching으로 구현
prefetching의 장점
공간과 시간은 함께 절약하기 힘들다.
• 계산 결과를 캐싱하려면 메모리 사용량이 필연적으로 높아진다.
• 계산값을 재생성하는데에는 시간이 들지 않는다.
• prefetching 방법을 쓰려면 미리 가져온 명령어나 데이터를 저
장해 둘 공간이 필요하다.
• 하지만 저장해 놓은 데이터나 명령어를 접근하는 데에 필요한
시간은 줄어든다.
• 결국 메모리를 많이 쓰면 속도가 빨라진다.
항목19: 임시 객체의 원류를 파악
이름 없는 임시 객체가 만들어지는 상황
• 함수 호출을 성사시키기 위해 암시적 타입변환이 적용될 때
• 함수가 객체를 값으로 반환(return by value)할 때
함수 호출 성사를 위한 임시 객체
임시객체 생성을 방지하는 방법
• 임시 객체가 함수 호출 성사를 위해 생성되었다가 소멸되는 일
은 편리하긴 하지만 불필요한 낭비이다.
• 이를 막는 일반적인 방법은 두 가지이다.
• 코드를 다시 설계해서 이런 변환이 일어나지 않게 하는 것
• 타입변환이 불필요하도록 소프트웨어를 수정하는 것
암시적 타입변환이 이루어지는 때
• 객체가 값으로 전달될 때 혹은 상수 객체 참조자(reference-to-
const)타입의 매개변수로 객체가 전달될 때
• 비상수 객체 참조자(reference-to-non-const) 타입의 매개변수
로 객체가 전달될 때에는 암시적 타입변환이 일어나지 않는다.
비상수 객체 참조자에 대해서 암시적 타입변환이 일어나지 않는다.
객체를 return할 때 임시 객체 생성
항목20: 반환값 최적화
생성자 인자 반환
inline으로 오버헤드 최소화
항목21: 오버로딩으로 불필요한 암시적 타입변환을 막기
오버로드로 불필요한 임시객체 생성 방지
항목22: op 대신에 op= 사용
사용자 정의 타입을 위한 op+=
단독 형태 연산자와 대입 형태 연산자
• 일반적으로 대입 형태 연산자는 단독 형태 연산자보다 효율적
이다. 단독 형태 연산자는 새 객체를 반환해야 하기 때문에, 임
시 객체를 생성하고 소멸하는 비용이 소모되지만, 대입 형태 연
산자는 왼쪽인자에다가 처리결과를 기록하기 때문에, 이 연산자
의 반환값을 담을 임시 객체를 만들어 놓을 필요가 없다.
단독 형태 연산자와 대입 형태 연산자
• 대입 형태 연산자와 단독 형태 연산자를 동시에 제공함으로써
클래스 사용자에게 효율과 편리성 사이에서 선택할 수 있는 기
회를 준다.
단독 형태 연산자와 대입 형태 연산자
단독 형태 연산자와 대입 형태 연산자
항목23: 정 안되면 다른 라이브러리 사용
• 이상적인 라이브러리는 작고, 빠르고, 강력하고, 유연하고, 확장
도 가능하고, 직관적이고, 어디든 쓸 수 있고, 플랫폼 지원도 좋
아야 하고, 사용상의 제약에 대해 자유롭고, 버그도 없어야 한다.
• 하지만 이는 이상일 뿐이다.
• 속도, 범용성, 견고성 등 중에서 무엇을 우선순위로 할 것인지
선택해서 라이브러리를 구현한다.
• 소프트웨어에서 사용하는 라이브러리만 교체해도 성능 향상을
이뤄낼 수 있다.
항목24: 가상함수, 다중 상속 등등 비용 파악
• virtual function
• virtual table(vtbl): 보통 함수 포인터의 배열(어떤 컴파일러는 배
열 대신에 linked list를 사용하기도 한다). 이 테이블은 가상 함
수를 선언했거나 상속받은 클래스에 무조건 생기고, vtbl의 각
요소는 해당 클래스에서 정의한 가상 함수 코드의 시작주소이
다.
가상함수와 가상함수 테이블
가상함수에 들어가는 비용
• 가상 테이블을 담는 메모리가 필요하다.
• 어떤 클래스에 대해 만들어지는 vtbl의 크기는 그 클래스에 선
언된 가상함수(기본 클래스에서 상속받는 것까지 합해서)의 수
에 비례한다.
• 한 클래스의 vtbl은 프로그램 이미지 안에 딱 하나만 있어야 하
는데, 컴파일러는 vtbl을 어디에 둘 것인가?
가상 테이블 포인터
• vptr
• 가상 함수를 선언한 클래스로부터 만들어진 객체에는 그 클래
스의 가상 함수를 가리키는 데이터 멤버가 하나 숨겨져 있다.
• vptr는 놓이는 객체 내의 위치는 컴파일러만 알고 있다.
• vptr은 가상 함수에 들어가는 두 번째 비용이다.
• 객체가 별로 크지 않은 경우 vptr의 비용은 만만치 않다.
수행 성능을 저하하는 가상 함수 비용
• 인라인(inline): 컴파일 도중에, 호출 위치에 호출되는 함수의 몸
체를 끼어 넣는다.
• 가상(virtual): 호출할 함수를 런타임까지 기다려 결정한다.
• 가상 함수는 함수의 인라인 효과를 포기해야 한다.
끔찍한 다중 상속 마름모꼴
가상 기본 클래스를 사용하면 포인터가 추가
가상 테이블 포인터를 추가
런티임 타입 식별(RTTI)
• runtime type identification
• 실행 중에 객체와 클래스의 정보를 알아낼 수 있게 하는 기능
• 그 정보를 저장해 둘 공간이 필요하다.
• C++에 의하면 객체의 동적 타입을 정확히 뽑아낼 수 있으려면
그 타입에 가상 함수가 최소한 하나 있어야 한다.
• 이는 가상 함수 테이블과 유사하다.
RTTI가 반영된 vtbl
가상 함수, 다중 상속, 가상 기본 클래스, RTTI 비용

Mec++ chapter3,4

  • 1.
    More effective C++ NHNNEXT 장문익
  • 2.
  • 3.
  • 4.
  • 5.
    리소스를 얻어내는 생성자와해제하는 소멸자가 필요한 경우
  • 6.
  • 7.
    항목10: 생성자에서 리소스누수 방지 • C++에서는 생성 도중에 예외를 일으킨 객체에 대해서는 마무 리 작업을 해주지 않는다. • 이런 작업이 필요하다면 프로그래머가 직접 생성자를 설계해야 한다. • try-catch를 사용하여 생성자를 구현할 수도 있다.
  • 8.
  • 9.
    try-catch를 멤버 초기화리스트 밖으로
  • 10.
    try-catch를 멤버 초기화리스트 밖으로
  • 11.
  • 12.
    항목11: 소멸자에서는 예외가탈출못하게 • 클래스 소멸자가 호출되는 경우는? • 객체가 통상적인 조건에서 소멸되는 것인데, 통상적인 조건이란 지역변수로 선언된 객체가 유효범위(scope)를 벗어났을때와 객 체가 직접 삭제(delete)될 때 • 예외 처리 매커니즘에 의해 객체가 소멸되는 것인데, 예외 전파 (exception propagation) 과정의 일부분으로 스택 되김가기 진 행될 때 • 즉, 소멸자가 호출되었을 때 예외일수도 아닐 수도 있다.
  • 13.
    C++ terminate함수 • 프로그램의실행을 끝장낸다. • 지역 객체조차도 소멸되지 않는다. • 어떤 예외에 대한 처리가 진행되고 있는 동안 또 다른 예외 때 문에 프로그램 흐름이 소멸자 함수를 떠나면, C++ terminate 함 수를 호출하게 된다. • 이를 고려하여 어떤 상황에서는 소멸자를 작성할 때 예외가 발 생된 상태에 있다고 가정하여야 한다.
  • 14.
  • 15.
    logDestruction에의 예외가 Session소멸자를 빠져나가지 못하게
  • 16.
    logDestruction에의 예외가 Session소멸자를 빠져나가지 못하게
  • 17.
    소멸자에서 발생한 예외가처리되지 않으면 소멸자는 실행이 끝나지 않는다
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
    예외 타입과 catch문이받는 타입 일치시키는 타입 변환 • 상속 기반의 변환
  • 25.
  • 26.
    catch() 문에서 허용되는타입변환 • 타입이 있는 포인터로부터 타입이 없는 포인터로 바꾸는 경우 • 즉, const void* 포인터를 받는 catch 문은 어떤 포인터 타입의 예외이든지 잡아 낼 수 있다. • catch (const void*) …
  • 27.
    매개변수 전달과 예외전파 사이 차이점3 • catch 문은 등장한 순서에 따라 사용된다. • 파생 클래스 타입을 받는 catch 문이 준비되어 있다고 해도 순 서가 제대로 되어 있지 않으면, 기본 클래스 타입을 받는 catch 문이 파생 클래스 타입의 예외를 잡아낼 수 있다.
  • 28.
  • 29.
    항목13: 발생한 예외는참조자로 • catch 문을 작성할 때에는 예외 객체가 전달되는 방식을 설정해 야 한다. • 포인터, 값, 참조자에 의한 전달이 가능하다.
  • 30.
  • 31.
    포인터에 의한 예외전달 •예외 객체가 힙에 할당된 주소라면 메모리 누수를 막기 위해 포 인터를 삭제해야 한다. • 그런데 예외 객체가 힙에 할당되지 않는다면? • 사용자에 따라 다르다. • 어떤 사용자는 전역 객체나 정적 객체의 주소를 넘길 수 있고, 어떤 사용자는 힙에 할당된 예외 객체의 주소를 넘길 수 있다. • C++ 기본 제공 표준 예외는 객체에 대한 포인터가 아니라 모두 객체이다. 포인터를 사용하면 이 예외들을 사용할 수 없다.
  • 32.
    값에 의한 예외받기(catch-by-value) •예외 삭제 고민에서 해방 • C++ 표준 예외와도 잘 맞다. • 전달되는 예외 객체는 늘 두 번씩 복사되어야 한다. • 슬라이스 문제(slicing problem): 발생 시에는 파생 클래스의 객 체였다가, 기본 클래스를 받는 catch 문에 들어가면 파생 클래 스 부분에 추가되었던 데이터가 싹둑 잘려 나가는 현상
  • 33.
  • 34.
    참조자에 의한 예외받기(catch-by-reference) •객체 삭제에 대한 고민이 필요 없다. • C++ 표준 예외를 처리하는 데에도 무리가 없다. • 슬라이스 문제도 없고 예외 객체는 한 번만 복사된다.
  • 35.
    항목14: 예외지정은 냉철하게사용 • 예외지정(exception specification): 함수가 발생시킬 예외를 미 리 지정할 수 있는 기능
  • 36.
  • 37.
    예외 지정 안된 함수를 호출하는 함수에는 예외 지정을 두지 않는다.
  • 38.
    시스템이 일으키는 예외를처리 • “시스템”이 일으킬 가능성이 있는 예외(C++ 표준 예외) • 이런 예외 중 가장 흔한 것이 bad_alloc • 이 예외는 메모리 할당에 실패한 operator new와 operator new[]가 발생시킨다.
  • 39.
    예기치 않은 예외를다른 타입의 예외로 대체
  • 40.
    예기치 않은 예외를다른 타입의 예외로 대체
  • 41.
    예외 지정의 고려할점 • 어떤 예외가 발생될지를 알려준다는 면에서 문서화에 도움이 된다. • C++는 예외 지정의 위배가 발생하면 기본적으로 프로그램을 일 시 중단시킨다. • 컴파일러는 예외 지정에 대해 부분적인 일치성 점검만 수행하 고, 프로그래머는 예외 지정의 일치성을 어기기 쉽다.
  • 42.
    항목15: 예외처리 비용을제대로 알자 • 예외 처리 기능을 전혀 쓰지 않았을 때에 비용은 • 어떤 객체가 생성 과정을 완료했는지 체크하는 데에 내부적으 로 사용되는 자료구조에 대한 메모리가 소모된다. • 이 자료구조를 업데이트하는 데에 필요한 시간이 소모된다. • 결론적으로 예외 처리 기능을 배제하고 컴파일한 프로그램은 예외 처리를 지원하도록 컴파일한 것에 비해 속도도 빠르고 크 기도 작다.
  • 43.
    try 블록으로 생기는비용 • try 블록이 소스에 들어가기만 하면, 즉 예외를 처리하겠다고 작 정하면 무조건 지불해야할 비용이다. • 예외 지정 기능에 대해서도 try 블록과 비슷한 양의 코드가 생성 되기 때문에, 예외 지정에 들어가는 try 블록과 비슷하다. • 예외가 발생되는 경우는 드물기 때문에 예외 발생 시 소모되는 비용은 큰 관심사가 되지 못한다. • 우선 가능하다면 예외 기능을 지원하지 않도록 컴파일한다.
  • 44.
    항목16: 80-20 법칙 •프로그램 리소스의 80%는 전체 실행 코드의 약 20%만이 사용 한다. • 실행시간의 80%는 실행 코드의 약 20%만 소모한다. • 메모리의 80%는 실행 코드의 약 20%만이 사용한다. • 디스크 접근 회수의 80%는 실행코드의 20%가 접근한 회수다. • 프로그램 유지보수에 들어가는 수고의 80%는 실행 코드의 20% 에 집중된다. • 아무 곳이나 골라잡고 효율을 향상시키려고 애쓰지 마라.
  • 45.
    프로그램 프로파일러 • programprofiler • 성능 향상을 만들어 낼 수 있는 20%를 판별하기 위해 사용한다. • 사용자가 관심을 두고 있는 리소스를 직접 측정해주는 도구가 필요하다. • 수행 성능 문제에 대처하는 최선의 길은 가능한 많은 데이터를 사용해서 소프트웨어를 프로파일링하는 것이다.
  • 46.
    항목17: 지연 평가(lazyevaluation) • 지연(to be lazy): 어떤 일을 하긴 하되 그 일을 하는 코드의 실 행을 피하는 방법 • 지연 평가: 지연 평가를 사용해서 만든 C++ 클래스는 어떤 처 리 결과가 진짜로 필요해질 때가지 그 처리를 미룬다. 어떤 컴퓨 팅 작업을 수행하는 데에 있어서 그 작업 결과가 진짜로 요구되 기 전에는 그것을 하지 않는다.
  • 47.
  • 48.
  • 49.
    지연 방식의 데이터가져오기(lazy fetching)
  • 50.
  • 51.
    mutable • const 멤버함수는 ‘보통의’ 클래스 데이터 멤버를 수정할 수 없 다. • mutable로 선언하면 “이 데이터는 어떤 멤버 함수에서도 수정 이 가능함“이란 뜻이다. • const 멤버도 수정할 수 있다.
  • 52.
    지연 방식의 표현식평가(lazy expression evaluation)
  • 53.
    수치 계산에 있어서지연 평가 • 두 값 사이의 의존 관계를 저장해야 한다. • 값과 의존 관계를 저장하는 자료구조도 필요하다. • 대입, 복사, 덧셈 같은 연산자에 대한 오버로딩도 필요하다. • 이런 수고로움이 따르지만 상당량의 수행 시간/가용 메모리 절 약을 해준다는 장점이 있다.
  • 54.
    항목18: 예상되는 계산결과를 미리 준비 • 과도 선행 평가(over-eager evaluation) • 현재 요구된 것 이외에 더 많은 작업을 미리 해둠으로써 소프트 웨어의 성능을 향상시킨다.
  • 55.
    과도 선행 평가방법이 필요한 경우
  • 56.
    이미 계산이 끝났고다시 사용될 것 같은 값을 캐싱하는 방법 • 데이터베이스에 대한 질의 반복하면 데이터베이스에 부하가 생 긴다. • 이를 방지하기 위해 이전에 뽑아 낸 데이터를 캐싱해두는 함수 를 만든다. • 이미 탐색한 데이터는 데이터베이스가 아닌 캐시를 통해 가져 오도록 만든다.
  • 57.
  • 58.
    미리가져오기(prefetching) • 디스크 컨토롤러는디스크에서 데이터를 읽을 때, 프로그램 쪽 에서 아주 적은 양만 요구했음에도 불구하고 블록 하나 혹은 섹 터 하나를 왕창 읽는다. • 조금씩 여러 번 읽는 것보다 한 번에 많이 읽는 쪽이 더 빠르다. • locality of reference
  • 59.
  • 60.
  • 61.
  • 62.
    공간과 시간은 함께절약하기 힘들다. • 계산 결과를 캐싱하려면 메모리 사용량이 필연적으로 높아진다. • 계산값을 재생성하는데에는 시간이 들지 않는다. • prefetching 방법을 쓰려면 미리 가져온 명령어나 데이터를 저 장해 둘 공간이 필요하다. • 하지만 저장해 놓은 데이터나 명령어를 접근하는 데에 필요한 시간은 줄어든다. • 결국 메모리를 많이 쓰면 속도가 빨라진다.
  • 63.
  • 64.
    이름 없는 임시객체가 만들어지는 상황 • 함수 호출을 성사시키기 위해 암시적 타입변환이 적용될 때 • 함수가 객체를 값으로 반환(return by value)할 때
  • 65.
    함수 호출 성사를위한 임시 객체
  • 66.
    임시객체 생성을 방지하는방법 • 임시 객체가 함수 호출 성사를 위해 생성되었다가 소멸되는 일 은 편리하긴 하지만 불필요한 낭비이다. • 이를 막는 일반적인 방법은 두 가지이다. • 코드를 다시 설계해서 이런 변환이 일어나지 않게 하는 것 • 타입변환이 불필요하도록 소프트웨어를 수정하는 것
  • 67.
    암시적 타입변환이 이루어지는때 • 객체가 값으로 전달될 때 혹은 상수 객체 참조자(reference-to- const)타입의 매개변수로 객체가 전달될 때 • 비상수 객체 참조자(reference-to-non-const) 타입의 매개변수 로 객체가 전달될 때에는 암시적 타입변환이 일어나지 않는다.
  • 68.
    비상수 객체 참조자에대해서 암시적 타입변환이 일어나지 않는다.
  • 69.
    객체를 return할 때임시 객체 생성
  • 70.
  • 71.
  • 72.
  • 73.
    항목21: 오버로딩으로 불필요한암시적 타입변환을 막기
  • 74.
  • 75.
  • 76.
  • 77.
    단독 형태 연산자와대입 형태 연산자 • 일반적으로 대입 형태 연산자는 단독 형태 연산자보다 효율적 이다. 단독 형태 연산자는 새 객체를 반환해야 하기 때문에, 임 시 객체를 생성하고 소멸하는 비용이 소모되지만, 대입 형태 연 산자는 왼쪽인자에다가 처리결과를 기록하기 때문에, 이 연산자 의 반환값을 담을 임시 객체를 만들어 놓을 필요가 없다.
  • 78.
    단독 형태 연산자와대입 형태 연산자 • 대입 형태 연산자와 단독 형태 연산자를 동시에 제공함으로써 클래스 사용자에게 효율과 편리성 사이에서 선택할 수 있는 기 회를 준다.
  • 79.
    단독 형태 연산자와대입 형태 연산자
  • 80.
    단독 형태 연산자와대입 형태 연산자
  • 81.
    항목23: 정 안되면다른 라이브러리 사용 • 이상적인 라이브러리는 작고, 빠르고, 강력하고, 유연하고, 확장 도 가능하고, 직관적이고, 어디든 쓸 수 있고, 플랫폼 지원도 좋 아야 하고, 사용상의 제약에 대해 자유롭고, 버그도 없어야 한다. • 하지만 이는 이상일 뿐이다. • 속도, 범용성, 견고성 등 중에서 무엇을 우선순위로 할 것인지 선택해서 라이브러리를 구현한다. • 소프트웨어에서 사용하는 라이브러리만 교체해도 성능 향상을 이뤄낼 수 있다.
  • 82.
    항목24: 가상함수, 다중상속 등등 비용 파악 • virtual function • virtual table(vtbl): 보통 함수 포인터의 배열(어떤 컴파일러는 배 열 대신에 linked list를 사용하기도 한다). 이 테이블은 가상 함 수를 선언했거나 상속받은 클래스에 무조건 생기고, vtbl의 각 요소는 해당 클래스에서 정의한 가상 함수 코드의 시작주소이 다.
  • 83.
  • 85.
    가상함수에 들어가는 비용 •가상 테이블을 담는 메모리가 필요하다. • 어떤 클래스에 대해 만들어지는 vtbl의 크기는 그 클래스에 선 언된 가상함수(기본 클래스에서 상속받는 것까지 합해서)의 수 에 비례한다. • 한 클래스의 vtbl은 프로그램 이미지 안에 딱 하나만 있어야 하 는데, 컴파일러는 vtbl을 어디에 둘 것인가?
  • 86.
    가상 테이블 포인터 •vptr • 가상 함수를 선언한 클래스로부터 만들어진 객체에는 그 클래 스의 가상 함수를 가리키는 데이터 멤버가 하나 숨겨져 있다. • vptr는 놓이는 객체 내의 위치는 컴파일러만 알고 있다. • vptr은 가상 함수에 들어가는 두 번째 비용이다. • 객체가 별로 크지 않은 경우 vptr의 비용은 만만치 않다.
  • 87.
    수행 성능을 저하하는가상 함수 비용 • 인라인(inline): 컴파일 도중에, 호출 위치에 호출되는 함수의 몸 체를 끼어 넣는다. • 가상(virtual): 호출할 함수를 런타임까지 기다려 결정한다. • 가상 함수는 함수의 인라인 효과를 포기해야 한다.
  • 88.
  • 89.
    가상 기본 클래스를사용하면 포인터가 추가
  • 90.
  • 91.
    런티임 타입 식별(RTTI) •runtime type identification • 실행 중에 객체와 클래스의 정보를 알아낼 수 있게 하는 기능 • 그 정보를 저장해 둘 공간이 필요하다. • C++에 의하면 객체의 동적 타입을 정확히 뽑아낼 수 있으려면 그 타입에 가상 함수가 최소한 하나 있어야 한다. • 이는 가상 함수 테이블과 유사하다.
  • 92.
  • 93.
    가상 함수, 다중상속, 가상 기본 클래스, RTTI 비용