Diese Präsentation wurde erfolgreich gemeldet.
Wir verwenden Ihre LinkedIn Profilangaben und Informationen zu Ihren Aktivitäten, um Anzeigen zu personalisieren und Ihnen relevantere Inhalte anzuzeigen. Sie können Ihre Anzeigeneinstellungen jederzeit ändern.

Mec++ chapter3,4

  • Als Erste(r) kommentieren

Mec++ chapter3,4

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

×