more effective c++ 2

26
Effective C++_2 131043 양현찬 NHN NEXT

Upload: -

Post on 13-Jul-2015

939 views

Category:

Software


5 download

TRANSCRIPT

Page 1: More effective c++ 2

Effective C++_2131043 양현찬

NHN NEXT

Page 2: More effective c++ 2

시작하기 전에 결론은

예외기능을 지원하지 않도록 컴파일 하라

Page 3: More effective c++ 2

리소스 누수방지는 소멸자로

• 지역리소스(스택 할당)를 물고 있는 포인터와 이별하자

• 매번 예외에 delete를 통해 처리해줘야 한다.

• Try-catch문은 코드가 복잡해질 뿐만 아니라 여러 문제를 안고 있다.

• 소멸자에 delete를 물고 있는 객체인 스마트 포인터를 사용하자

• 윈도우 시스템 API들은 c스타일의 인터페이스를 가지고 있다.

Page 4: More effective c++ 2

윈도우 시스템 API들의 관리법

• 리소스를 얻어내는 생성자와 해제하는 소멸자를 가진 객체를 이용하여 c스타일 API들을 관리하자

Page 5: More effective c++ 2

생성자에서는 누수가 일어나지 않도록

• Operator new는 예외를 발생시킬 위험이 있다.

• C++에서는 생성이 안전하게 완료된 객체에 관해서만 소멸자를호출한다.

• 이런 점을 염두하고 생성자를 설계해야 한다.

• 스마트 포인터를 사용하면 까다로운 설계문제들을 쉽게 해결할수 있다.

Page 6: More effective c++ 2

소멸자에서는 예외가 탈출할 수 없도록

• 소멸자가 호출되는 경우는 두 가지이다.

• 첫째는 통상적인 소멸이다. 즉 지역변수로 생성된 객체가 유효범위에서 벗어나 소멸되는 경우이다.

• 둘째의 경우는 예외처리 메커니즘에 의해 객체가 소멸될 경우이다.

• 하지만 소멸자 안에서는 어느 경우에 호출되었는지 알 수 있는방법이 없다.

Page 7: More effective c++ 2

소멸자 작성시 예외가 발생한 상태로 가정하자

• 소멸자를 작성할 때 방어적으로 작성해야 한다.

• 예외처리가 진행되고 있는 도중 또 다른 예외 때문에 흐름이 소멸자 함수를 떠나면 C++에서는 terminate함수를 호출한다.

• Terminate함수 호출 시 칼같이 종료시켜버리기 때문에 지역객체조차도 소멸되지 않는다.

• 따라서 소멸자 안에서 예외가 발생할 경우 소멸자를 탈출하게 되는 경우를 만들지 말아야 한다.

Page 8: More effective c++ 2

예외 발생은 매개변수전달, 가상함수 호출과 다르다.

• 함수의 매개변수 선언과 catch문은 생긴 것도 역할도 비슷하다.

• 함수의 호출은 함수 종료시 흐름이 호출한 부분으로 다시 돌아오지만 catch문은 throw를 호출한 부분으로 다시 돌아오지 않는다.(goto문과 비슷하다. 흐림을 깨버린다.)

• 예외를 처리할 때 인자가 Catch by value가 되든 Catch by reference가 되든 사본생성이 이루어진다. 다만 사본 생성 횟수가 다르다. Value는 2번 Reference는 1번 생성된다.

• 포인터에 의한 전달은 함수랑 같다.

• 지역객체에 대한 포인터는 넘겨봐야 소용없다. 결국은 유효범위를 넘어서면 소멸되기 때문이다.

Page 9: More effective c++ 2

동적 타입과 정적 타입 복사

• 예외를 던질 때는 상수 참조자를 사용할 필요는 없다. 그냥 참조자를 사용할 수 있다.

• 예외를 전파하는 경우 rethrow를 이용하자. Rethrow는 객체의 복사가 일어나지 않는다.

• Rethorw란 catch문안에서 throw;를 했을 때를 뜻한다.

Page 10: More effective c++ 2

예외와 타입변환

• 기본적으로 예외는 암시적인 타입변환이 일어나지 않는다.

• 다만 상속관계의 타입과 포인터로부터 void*로의 타입변환은 허용된다.

• 예외처리 메커니즘은 „가장 첫째의 것을 선택하는‟ 메커니즘을 따릅니다.

Try catch문 자동완성을 vs에서 사용하면 이

렇게 기본적으로 3개의 catch문이 생성된다.

아래로 갈수록 부모이다.

Page 11: More effective c++ 2

발생한 예외는 참조자로

• C++에서 제공하는 표준예외는 전부 포인터가 아니다.

• Catch by pointer는 좋은 방법이 아니다.

• 전역변수나 힙에 할당된 경우에만 유용할 뿐더러 관리도 힘들고delete여부도 알기 힘들다.

• Catch by value는 2번 사본이 생성될 뿐만 아니라 slicing problem을 가지고 있다.

• Slicing problem은 부모의 객체영역만 복사되어 자식의 영역이잘려나가는 현상이다.

Page 12: More effective c++ 2

예외지정기능은 냉철하게

• 함수가 발생시킬 예외를 미리 생성할 때 지정하는 것을 예외지정이라고 한다.

• 예외지정에 일관성이 없을 경우 컴파일 도중 발견해주고 함수가예외지정 리스트에 없는 예외를 발생시킬 경우 런타임 에러와 함께 unexpected라는 특수 함수가 자동으로 호출된다.

• 문제는 unexpected함수가 terminate를 호출하는데 이에 대해서는 알고 있듯 그냥 프로그램이 멈추어 버린다.

• Unexpected함수는 매우 쉽게 호출될 수 있는 함수이기 때문에예외지정은 신중하게 사용해야 한다.

Page 13: More effective c++ 2

Unexpected함수의 나락을 막는 법

• 예외 지정된 새로운 코드와 예외지정이 안된 옛날 코드가 섞일수 있기 때문에 조심하자

• 예외지정이 안 된 함수를 호출할 가능성을 가진 함수에는 예외지정을 두지 않는다.

• 템플릿과 예외지정은 어울리지 않는다 사용하지 말자

• “시스템”이 일으킬 가능성이 있는 예외(C++표준 예외)를 처리하자

• 아래와 같은 명령을 통해 unexpected함수를 대체할 사용자의 함수를 만든다.

Page 14: More effective c++ 2

예외처리에 드는 비용

• 예외처리를 검사하기 위해서 들어가는 내부적인 비용은 존재한다.

• 예외기능이 들어가지 않는 라이브러리를 제작하기로 결정했다면예외기능 없이 컴파일 하는 것이 코드 최적화에 상당한 기능을준다.

• Try블록으로 인해 생기는 비용이 있다. 쓸데없이 try블록을 남발해서는 안 된다.

• 우선 가능하다면 예외기능을 지원하지 않도록 컴파일 하라.

Page 15: More effective c++ 2

80 - 20

• 프로그램 리소스의 80%는 전체 실행 코드의 20%만이 사용한다.

• 실행의 핵심을 담당하는 코드 20%를 이해하고 이 부분을 효율적으로 만들어야 한다.

• 대부분의 프로그래머들은 자신이 짠 코드가 어느 정도의 수행 성능을 가지고 있는지 파악하지 못하고 있다.

• 프로파일러를 사용하여 20%부분을 찾아내자

• 20%를 뚫어보는 능력이 필요하다.

Page 16: More effective c++ 2

지연평가는 충분히 고려할 만하다

• 작업 결과가 진짜로 필요해질 때까지 그 처리를 미룬다. 예를 들어 게임 프레임 60이하로 떨어지기 전까지는 고려하지 않아도 된다.

• 진짜 필요해지기 전까지는 자기만의 데이터 사본을 만들지 않고남의 것을 끌어다 사용한다

• „지연된 데이터 가져오기‟ 해당하는 데이터가 진짜로 필요할 경우에만 가져오는 기법이다. 그 전까지는 메모리에는 객체의 껍데기만 있다.

• 결과가 같은 동작을 여러 번 해야 할 경우 미리 그 결과를 저장해놓고 사용한다.

Page 17: More effective c++ 2

예상되는 결과를 미리 준비하자

• 미리 준비하면 처리 비용을 절약할 수 있다.

• 상당히 자주 요구될 것 같은 계산이 있다면, 그 요구를 효율적으로 처리할 수 있는 자료구조를 설계하여 비용을 낮추자.

• STL사용시 iter->second가 아닌 *(iter).second 이런 형태로iterator를 사용하자

• CPU의 연산을 메모리로 돌릴 수 있다. 즉 메모리를 쓸 수록 속도가 빨라진다.

Page 18: More effective c++ 2

임시 객체의 근원을 정확히 이해하자

• 임시 객체란 우리코드에 보이지 않는 임시로 만들어진 „이름 없는‟ 객체이다.

• 함수호출을 성사시키기 위한 암시적 타입변환이 적용 될 때 생성된다.

• Return by value를 할 때 생성된다.

• 암시적 타입 변환이 일어나는 경우는 오직 객체가 값으로 전달될 경우와 reference to const(상수 참조자)가 전달될 경우이다.

• 객체를 value로 반환하는 대부분의 함수는 반환 시 임시객체를피할 수 없다. 컴파일러의 최적화만 믿을 뿐이다.

Page 19: More effective c++ 2

반환 값 최적화가 가능하도록 하자

• 지역 변수의 값을 참조자로 반환하거나 포인터로 반환하는 일은하지 말자.

• 컴파일러가 최적화 가능한 형태로 코드를 작성하자.

Page 20: More effective c++ 2

오버로딩은 불필요한 암시적 타입변환을 막는다.

• 암시적 타입변환은 정말 편리하지만 임시객체의 생성은 우리가원하지 않는다.

• 함수의 오버로딩을 통해 다양한 경우에 대해 준비함으로써 암시적 타입변환을 막을 수 있다.

• 오버로딩 되는 연산자 함수는 반드시 최소한 한 개 이상의 사용자타입이 인자로 들어가야 한다.

• 물론 프로그램의 효율은 고려하자 사용하지도 않는 함수를 쌓아놓지 말자.

Page 21: More effective c++ 2

+보다 +=을 사용할 수 있으면 하자

• 연산자를 만들어서 사용할 때 대입 연산자를 통해 단독 연산자를작성하면 자연스럽게 두 연산자를 관계 지어 둘 수 있다.

• 대입 연산자는 임시객체를 만들지 않기 때문에 효율적이다.

• 대입 연산자는 매우 편리하다.

• 대입 연산자를 통해 단독 연산자를 작성할 때 이름없는 형태의임시객체를 만들어 반환 할 수 있기 때문에 임시객체 최적화를컴파일러에게 부탁할 수 있다.(슬라이드 19장 참고)

Page 22: More effective c++ 2

정 안 되면 다른 라이브러리 쓰자

• 사용하는 라이브러리에서 병목현상이 발견된다면 그냥 라이브러리를 바꿔라. 비슷한 걸로.

• 라이브러리만 바꿔도 눈에 띄게 수행성능이 달라질 수 있다.

• Stdio가 iostream보다 지금은 빠르지만 iostream이 stdio보다 빨라질 수 있는 날이 올 수도 있다.

• 아마 그땐 내가 은퇴했을지도(….)

Page 23: More effective c++ 2

가상함수, 다중상속, 가상 기본 클래스, RTTI 의 비용

• 가상함수가 생성될 때 가상 테이블(vtbl)과 가상 테이블 포인터(vptr)가 함께 생성된다.

• Vtbl은 함수포인터의 배열이며 가상함수의 선언 뿐만 아니라 상속받은 클래스에도 생성된다. 그리고 가상함수의 시작주소(함수포인터)를 물고있다.

• 컴파일러가 필요한 목적파일마다 vtbl을 생성하고 링커가 중복을제거해주거나 heuristic이라는 규칙을 사용해서 vtbl을 가질 목적파일을 결정해준다.

• Inline도 아니고 순수가상 함수도 아닌 함수 중 첫 번째 정의부분에 vtbl을 넣기로 하는 규칙이다.

• Heuristic규칙은 가상함수를 inline으로 너무 많이 선언할 때 적용할 수 없게 된다.

Page 24: More effective c++ 2

가상 함수

• Virtual의 의미는 “호출 할 함수를 런타임까지 기다려서 결정한다.” 이다.

• Vptr은 „어떤 객체에 대해 어떤 vtbl을 사용할 것인가‟를 결정한다.

• 해당하는 클래스의 숨겨진 데이터 멤버 변수형태로 선언된다.

• 가상함수만 놓고 수행성능의 발목을 잡는다고 보기는 힘들다. 고작 명령어 몇 개가 더 실행되는 수준이다.

• 가상함수는 inline을 포기해야 한다. (참조자나 포인터를 사용할경우에만 해당되지만 일반적으로 참조자나 포인터만 사용한다.)

Page 25: More effective c++ 2

가상 기본 클래스

• 이것이 없으면 하나의 파생클래스에서 기본클래스까지의 길이두 가지일 때 기본클래스가 중복되는 현상이 발생하기 때문에 필요하다.

• 잘 생각하고 사용해야 한다. 각각에 대해서 vptr과 vtbl이 생성되기 때문이다.

Page 26: More effective c++ 2

RTTI 런타임 타입 식별

• RTTI는 실행 중에 객체와 클래스의 정보를 알아낼 수 있지만 이정보를 저장할 메모리가 당연히 필요하다.

• 이 정보는 type_info라는 객체에 저장되며, typeid연산자를 사용해서 액세스할 수 있다.

• RTTL은 vtbl을 통해서만 구현될 수 있도록 설계되었기 때문에 해당 클래스에 가상함수가 하나는 꼭 들어있어야 한다.