38
Effective C++ 정정 131054 정정정

effective c++ chapter 3~4 정리

Embed Size (px)

DESCRIPTION

effective c++ chapter 3~4 정리

Citation preview

Page 1: effective c++ chapter 3~4 정리

Effective C++ 정리131054 이인재

Page 2: effective c++ chapter 3~4 정리

항목 13 : 자원 관리에는 객체가 그만

• 팩토리 함수로 얻은 객체는 호출자에서 그 객체를 삭제해야 한다 .

• 호출자 f() 에서 delete pInv 가 실행될지 여부는 장담할 수 없다 .ex)return, go to, continue, 그외 예외상황 등등

• 해결책 : 자원을 객체에 넣고 그 자원 해제를 소멸자가 맡도록 한다.

Page 3: effective c++ chapter 3~4 정리

스마트 포인터 auto_ptr

• 스마트 포인터 auto_ptr 사용

• 가리키고 있는 대상에 대해 스마트 포인터의 소멸자가 자동으로 delete 를 호출

• auto_ptr 의 소멸자를 통해서 pInv 를 삭제

• auto_ptr 의 개수가 둘 이상이 안되도록 주의할 것 -> 지운 메모리 공간을 또 지울 수 있다 .

Page 4: effective c++ chapter 3~4 정리

스마트 포인터 RCSP

• 어떤 자원을 가리키는 외부 객체의 개수 유지

• 그 개수가 0 이 되면 해당 자원 자동으로 삭제

• auto_ptr 이 제공하지 못하는 복사기능 제공

• pInv1 과 pInv2 는 동시에 그 객체를 가리키고 있음

Page 5: effective c++ chapter 3~4 정리

스마트 포인터 사용시 주의사항

• auto_ptr 및 tr1::shared_ptr은 소멸자 내부에서 delete연산자 사용 -> delete[] 연산자 아님

• 동적으로 할당한 배열에는 스마트 포인터를 사용하면 안됨

• shared_ptr 이 auto_ptr 보다 복사 시의 동작이 더 직관적

• auto_ptr 은 복사되는 객체를 null 로 만들어버림

Page 6: effective c++ chapter 3~4 정리

항목 14 : 자원 관리 클래스의 복사 동작에 따른 고찰

• 힙에 생기지 않는 자원은 auto_ptr 혹은 tr1::shared_ptr 등의 스마트 포인터로 처리해 주기엔 맞지 않음

• 자원을 관리하는 객체를 복사를 하면 어떻게 작동할 지 고찰해 보아야 한다 .

Page 7: effective c++ chapter 3~4 정리

관리객체가 복사될 때 동작• 복사를 금지한다 -> RAII 객체가 복사되는 것 자체가

말이 안됨 ( private 멤버로 만들어서 복사금지 )

• 관리하고 있는 자원에 대해 참조 카운팅 수행 -> shared_ptr 을 이용하여 동작 가능 ( 참조자가 == 0 이면 기본적으로 shared_ptr 은 삭제되나 따로 삭제자를 지정할 수 있음 )

• 관리하고 있는 자원을 진짜로 복사 -> 깊은 복사 수행

• 관리하고 있는 자원의 소유권을 옮김 -> auto_ptr 의 복사

Page 8: effective c++ chapter 3~4 정리

자원 관리 클래스의 복사동작 고찰 정리

• 자원 관리 클래스가 동작하는 복사 문제는 어떻게 하느냐에 따라 복사 동작이 결정되기 때문에 주의해서 구현해야 함

• 일반적으로 자원 관리 클래스의 복사 동작은 복사를 애초에 금지하거나 , 참조 카운팅을 해주는 선에서 마무리 하는 것이 일반적임 ( 다른 방법들도 가능은 함 )

Page 9: effective c++ chapter 3~4 정리

항목 15 : 자원 관리 클래스에서 관리되는 자원을 외부에서 접근할 수 있도록 하자

• 프로그래밍 시 실제 자원을 써야만 하는 상황이 있음

• 자원 관리 클래스가 감싸고 있는 실제 자원에 접근할 수 있는 방법이 필요함

• 대표적인 방법에는 명시적 변환과 암시적 변환이 있음

• daysHeld() 라는 함수에 Invetment 포인터 형식의 pInv1 객체를 넘겨주지만 에러가 난다 -> pInv1 은 auto_ptr 형태이기 때문이다 .

Page 10: effective c++ chapter 3~4 정리

명시적 변환

• 명시적 변환은 눈에 보이는 형 변환

• 스마트 포인터인 shared_ptr 및 auto_ptr 은 명시적 변환을 수행하는 get 이라는 멤버함수 제공

• get() 멤버함수는 스마트 포인터형태가 아닌 실제 자원이 가진 형태의 포인터 값을 반환함

• auto_ptr 의 멤버함수 get() 을 이용하여 daysHeld() 라는 함수에 Investment 의 포인터 형태 ( 실제 자원 형태 ) 로 넘겨준다 .

Page 11: effective c++ chapter 3~4 정리

암시적 변환

• 스마트 포인터인 shared_ptr 및 auto_ptr 은 암시적 변환도 가능

• operator * 또는 operator ->을이용하여 직접 자원에 접근하여 사용 가능하다 .

• operator* 또는 operator-> 를 사용 하여 직접 자원에 접근 할 수 있다 . ( GetID() 는 Investment 클래스의 멤버함수이다 )

Page 12: effective c++ chapter 3~4 정리

외부에서 실제 접근하는 방법 정리

• 자원 관리 클래스는 관리하고 있는 실제 자원에 대한 접근 API 를 많이 제공하기 때문에 이를 이용하여 실제자원에 접근 가능함

• 실제 자원에 접근하는 방법에는 명시적 변환과 암시적 변환이 있음

• 명시적 변환은 안정성에서 암시적 변환보다 좋지만 편의성을 놓고 본다면 암시적 변환이 좋다-> 케이스 바이 케이스로 사용

Page 13: effective c++ chapter 3~4 정리

항목 16 : new 및 delete 를 사용할 때는 형태를 맞춤

• new 연산은 메모리가 할당 된 후 생성자 호출

• delete 연산은 할당된 메모리에 대해 한 개 이상의 소멸자 호출 후 메모리 해제

• 소멸자가 호출되는 횟수만큼 delete 연산 적용

• 다음의 예제 코드는 에러 발생 (때에따라 이상하게 동작 )

• string * 의 변수는 총 100 개의 string 동적 배열을 할당하고 첫번째의 주소를 가리킨다 . 이때 delete str 을 하면 배열 크기만큼의 동적 할당 된 메모리를 해제 할 수 없다 .

Page 14: effective c++ chapter 3~4 정리

new,delete new[], delete[]

• 포인터에 대해 delete 를 적용 할 때 delete 연산자로 하여금 ‘배열 크기 정보가 있다’ 고 알려줄 수 있는 것은 프로그래머

• delete[] 라고 해야 그렇게 알려주는 것

• delete[] 연산시 배열 크기 만큼 해제 가능

• delete 는 단일 객체로 간주

• string * 의 변수는 총 100 개의 string 동적 배열을 할당하고 첫번째의 주소를 가리킨다 . 이때 delete []str 을 하면 배열 크기만큼의 동적 할당 된 메모리를 해제 할 수 있다 .

Page 15: effective c++ chapter 3~4 정리

new 와 delete 정리

• new 에 [] 을 썼다면 delete 에도 [] 를 써야 함

• new 에 [] 을 안썼다면 delete 에도 [] 를 쓰지 않아야 함

• 짝을 맞추지 않으면 예측할 수 없는 에러발생 가능

Page 16: effective c++ chapter 3~4 정리

항목 17 : new 로 생성한 객체를 스마트 포인터에 저장하는 코드는 별도의 한 문장으로 만들자

• 컴파일러는 가각의 연산이 실행되는 순서가 다름

• 이때 예제코드에서 보는 것과같이 아직 widget 이라는 객체가 생성되어 shared_ptr 에 넘어가기 전에 priority() 가 실행될 수 있음 -> priority 에서 자원 누출이 생길 수 있음

• priority() 라는 함수가 widget 객체가 생성되어 shared_ptr 에 넘어가기 전에 실행이 된다면 priority() 에서 예외가 발생하면 자원의 누출이 생길 수 있음

Page 17: effective c++ chapter 3~4 정리

컴파일러 제작사마다 연산 실행순서가 다름

• 컴파일러 제작사마다 연산 실행순서가 다르기 때문에 다음과 같이 해주어야 함

• Widget 객체를 동적으로 생성해서 스마트 포인터에 저장하는 것을 따로 한줄 별도로 만들어야 함

• 중간에 Priority() 가 끼어들 여지를 없애버리는 것

• 애초에 객체를 생성후 포인터가 가리키게 하는 연산 자체를 별도의 한 줄로 처리하면 된다 .

Page 18: effective c++ chapter 3~4 정리

동적 할당한 객체의 포인터 저장 정리

• 컴파일러마다 연산 순서가 다르므로 자원 누출을 막으려면 new 로 생성한 객체를 포인터에 저장하는 코드는 별도로 만드는 것이 좋음

• 이렇게 하지 않을 시 예외가 발생했을 때 디버깅이 힘들고, 예상치 못한 자원 누출 가능성이 생김

Page 19: effective c++ chapter 3~4 정리

항목 18: 인터페이스 설계는 제대로 쓰기엔 쉽게 , 엉터리로 쓰기엔 어렵게 하자

• 사용자가 썼을 때 뭔가 이상하면 컴파일이 되지 않아야 하고 , 잘 되면 제대로 프로그램이 동작해야 함

• 이런 경우 새로운 타입을 들여와 인터페이스를 강화하여 보완할 수 있음

• 각 타입의 값에 제약을 가하여 보완할 수 있음

• const 키워드를 이용하여 타입의 제약 부여 가능

• 언뜻 클래스의 설계가 제대로 되어있는 듯 하지만 Date(30,40,500);Date(40,20,51) 등의 실수에 대처할 수 없도록 되어 있으면 실제 사용자가 사용하는 데 있어 문제를 일으킬 수 있음

Page 20: effective c++ chapter 3~4 정리

인터페이스 설계시 일관성을 지키자

• STL 인터페이스는 전반적으로 일관성을 갖고 있음

• 일관성이 없으면 매번 그것에 대해 인식하고 있어야함->언젠가 실수할 수 있음

• 문제가 생길 여지가 없도록 설계하고 프로그램 하는 것이 좋음

•CreateInvestment() 는 Investment 의 포인터를 넘겨주는데 이때 프로그래머는 이 포인터를 해제해야 한다는 것을 항상 생각해야 한다 ( 실수의 여지가 있음 ) -> 하지만 shared_ptr 로 바로 넘겨주면 애초에 그런 실수의 여지 자체를 없애버림

Page 21: effective c++ chapter 3~4 정리

인터페이스 설계 정리

• 좋은 인터페이스란 제대로 쓰기에는 쉽고 , 어렵게 쓰기에는 어려운 인터페이스

• 인터페이스의 일관성을 유지해야 함

• 사용자의 실수 방지를 위해 새로운 타입으로 만들거나 , 제약조건 , const 를 이용하여 제한을 하면 좋음

Page 22: effective c++ chapter 3~4 정리

항목 19: 클래스 설계는 타입 설계와 똑같이 취급하자

• 새로 정의한 타입의 객체 생성 및 소멸은 어떻게 이루어져야 하는가?

• 객체 초기화는 객체 대입과 어떻게 달라야 하는가 ?

• 새로운 타입으로 만든 객체가 값에 의해 전달되는 경우에 어떤 의미를 줄 것인가 ?

• 새로운 타입이 가질 수 있는 적법한 값에 대한 제약은 무엇으로 잡을 것 인가 ?

• 기존의 클래스 상속 계통망에 맞출 것인가 ?

• 어떤 종류의 타입 벼환을 허용할 것인가 ?

Page 23: effective c++ chapter 3~4 정리

항목 19: 클래스 설계는 타입 설계와 똑같이 취급하자

• 어떤 연산자와 함수를 두어야 의미가 있을까 ?

• 표준 함수들 중 어떤 것을 허용하지 말 것인가 ?

• 새로운 타입의 멤버에 대한 접근권한을 어느 쪽에 줄 것인가?

• ‘ 선언되지 않은 인터페이스’로 무엇을 둘 것인가 ?

• 새로 만드는 타입이 얼마나 일반적인가 ?

• 정말로 꼭 필요한 타입인가 ?

Page 24: effective c++ chapter 3~4 정리

항목 20: ‘ 값에 의한 전달’보다는 ‘상수객체 참조자에 의한 전달’방식이 대개 낫다

• C++ 함수는 객체 전달 시 ‘값에 의한 전달’ 방식을 사용 (C 에서 물려받은 특성 )

• 함수의 매개변수는 실제 인자의 ‘사본’을 통해 초기화

• 일일이 복사해야 하므로 비용이 많이 들어감 • 값에 의한 전달은 실제 인자의 ‘사본’을 만들어

초기화 하며 이 ‘사본’에 의해 수행된다 . 자식 클래스 생성 전에 부모가 생성이되고 , 자식이 생성되기 때문에 시간적 비용이 많이 들어간다

Page 25: effective c++ chapter 3~4 정리

참조자를 이용하여 전달

• 상수객체에 대한 참조자를 전달하여 만들면 복사 생성자가 호출되지 않으므로 , 새로 만드는 비용이 들지 않음

• 전달된 객체는 실제 객체를 나타내므로 값이 바뀌지 않도록 안정성을 원한다면 const 키워드를 붙일 것

• 참조에 의한 전달 방식은 복사손실 문제도 해결

• 참조자에 의한 전달은 사본을 만드는 것이 아닌 실제 객체에 대해 참조를 하는 것이므로 복사에의한 비용이 들어가지 않는다 .

Page 26: effective c++ chapter 3~4 정리

복사손실문제• 자식 ( 파생 ) 클래스의 객체를 ,

부모 ( 기본 ) 클래스의 객체로 전달되는 경우 자식클래스의 특성이 잘려나가는 문제가 발생함

• 참조자를 이용한 전달을 이용하면 복사손실 문제 해결

• CheckCry(const Animal& pCat) 으로 해결가능

• 자식의 객체를 전달하여 , 자식 객체의 멤버함수를 호출하는것이 의도이지만 , 부모클래스의 객체로 전달되어 자식클래스의 특성이 잘려나감

Page 27: effective c++ chapter 3~4 정리

항목 20 : 참조자에 의한 전달 정리

• ‘ 값에 의한 전달’ 보다는 ‘상수 객체 참조자에 의한 전달’을 선호해야 함-> 대체적으로 효율적일 뿐만 아니라 복사손실 문제 해결

• 항상 참조자의 전달이 쓰이는 것은 아님-> 기본 제공 타입 , STL 반복자 , 함수 객체 타입은 ‘값에 의한 전달’이 더 적절

Page 28: effective c++ chapter 3~4 정리

항목 21: 함수에서 객체를 반환해야 할 경우에 참조자를 반환하려고 들지 말자

• 참조자는 그냥 이름이다 , 존재하는 객체에 붙는 별명과도 같다

• 함수 수준에서 새로운 객체를 만드는 방법은 2 가지 스택 또는 힙에 만드는 것

• 참조자를 쓰려면 이미 그 전에 객체가 생성되어 있어야 한다

• 스택에서 객체를 생성했다고 하더라도 함수를 빠져나오면 스택에 생성된 변수는 소멸하므로 객체 또한 소멸해서 없어진다 -> 참조자는 그 어떤것도 가리키지 못한다 .

Page 29: effective c++ chapter 3~4 정리

힙에서 객체를 만들어서 참조자로 전달하면 delete 를 할 수 없다

• 참조자는 그냥 이름이다 , 존재하는 객체에 붙는 별명과도 같다

• 힙에다가 객체를 만들었다고 하더라도 반환되는 참조자 뒤에 숨겨진 포인터에 대해서 사용자가 어떻게 접근할 방법이 없어서 delete 를 할 수 없다 • 말도 안되는 코드이다 . 힙에서 생성해서 넘겨주더라도

이 객체를 이제 어떻게 delete 를 할 것인지는 알 수 없다 .

Page 30: effective c++ chapter 3~4 정리

참조자를 반환에 대한 정리

• 새로운 객체를 반환하는 함수를 만드려면 새로운 객체를 만들어서 그 객체를 전달해야 함

• 지역 스택 객체에 대한 포인터나 참조자를 반환 하는 일은 절대 하지말아야 함

• 힙에 할당한 객체에 대한 참조자를 반환하는 일 , 지역 정적 객체에 대한 포인터나 참조자를 반환 하는 일은 절대로 하지 말아야 함

Page 31: effective c++ chapter 3~4 정리

항목 22:데이터 멤버가 선언될 곳은 private

• 객체지향의 대표적 성질인 캡슐화를 위하여 데이터 멤버는 private 에 선언

• 문법적 일관성을 생각하여 데이터 멤버는 private 로 선언-> 멤버 데이터에 어떻게 접근해야되는지 중구난방이면 쓰기에 매우 혼동이 올 수 있다 . • 데이터 멤버는 private 로 선언 , 데이터 멤버를

접근하는데 있어서는 public 으로 선언

Page 32: effective c++ chapter 3~4 정리

데이터 멤버가 private 로 선언에 대한 정리

• 문법적 일관성

• 객체지향의 특성인 캡슐화

• 데이터 멤버에 대한 세밀한 접근제어 가능

• 유지 , 보수 , 수정 시 public 이 바뀐다면 그 public 을 직접 접근하는 모든 코드들을 수정해야 함

• 참고 : protected 가 public 보다 안전한 것이 아님

Page 33: effective c++ chapter 3~4 정리

항목 23: 멤버 함수보다는 비멤버 비프렌드 함수와 더 가까워지자

• 멤버 범수로 여러 멤버함수를 호출한다면 캡슐화에서 오히려 좋지 않다

• 비멤버함수를 이용하면 컴파일 의존도를 낮출 수 있고 , 확장성 또한 좋아진다

• 비멤버 함수를 이용해야 캡슐화 및 확장성에서 효율

Page 34: effective c++ chapter 3~4 정리

비멤버 비프렌드 함수에 대한 정리

• 어떤 클래스의 private 멤버 부분을 접근할 수 있는 함수의 개수에 영향을 주지 않음->캡슐화 정도가 멤버함수보다 좋다

• 패키징 유연성과 기능적인 확장성도 비멤버 비프렌드 함수를 사용할 때 더 좋다

Page 35: effective c++ chapter 3~4 정리

항목 24: 타칩 변환이 모든 매개변수에 대해 적용되어야 한다면 비멤버 함수를 선언하자

• 암시적 타입 변환에 대해 매개변수가 먹혀들려면 매개변수 리스트에 들어 있어야만 한다

• 멤버 함수의 반대는 프렌드 함수가 아니라 비멤버 함수이다

• 어떤 함수에 들어가는 모든 매개변수에 대해 타입 변환을 해 줄 필요가 있다면 , 그 함수는 비멤버이어야 한다

Page 36: effective c++ chapter 3~4 정리

항목 25: 예외를 던지지 않는 swap 에 대한 지원도 생각해보자

• std 에서 제공하는 swap 함수는 자주 사용된다

• swap 함수를 구현함에 있어 비효율적인으로 작동할 때는 따로 특수화 해주어서 효율을 올릴 필요가 있다

• 기존 swap 은 temp 에 a 를 복사 , b 를 a 에 복사 , b 에 temp 를 복사하여 값을 맞바꾸기 한다

Page 37: effective c++ chapter 3~4 정리

포인터를 이용하여 값을 복사하는 비용을 줄이자

• template<> 의 특수화 이용

• 타입이 Widget 이라는 경우일 때는 다음의 함수르 구현하라는 뜻

• 실제 객체를 복사해서 바꾸는 것이 아닌 데이터를 가리키는 포인터들만을 바꾸어 주도록 하는 것이 pImpl 기법

• Widget 객체를 서로 바꾸기 위해 각자의 실제 데이터를 가리키고 있는 포인터객체 (pImpl) 포인터만 바꾸어 주면 된다

Page 38: effective c++ chapter 3~4 정리

std swap 함수의 정리

• 기본적으로 값을 바꿀 때 복사를 이용하여 값을 바꿈

• 효율이 떨어질 때에는 pImpl 포인터를 이용하여 실제 데이터를 가리키는 포인터만 변경해 줌

• 사용자 정의 타입에 대한 std 템플릿을 완전 특수화 하는 것도 가능

• std 에 새로 ‘추가’하는 것은 하지 말아야 함