이펙티브 C++ 공부

16
effective c++ 자자자자 , 자자 자 자자

Transcript of 이펙티브 C++ 공부

Page 1: 이펙티브 C++ 공부

effective c++ 자원관리 , 설계 및 선언

Page 2: 이펙티브 C++ 공부

자원 관리

Page 3: 이펙티브 C++ 공부

factory 함수는 반환 값으로 동적 할당된 자원을 가리키는 포인터를 반환한다 .

이러한 류의 자원을 잘 관리하기 위해 자원관리 객체를 활용하자 .

팩토리 함수로 만든 자원을 어떤 함수 내에서 사용한 후 delete 로 반환할 때 , 올바르게 delete 까지 도달하리라는 보장이 없다 .( 중간에 예외나 오류가 발생할 가능성이 있다 .)

이 때 , 자원을 그냥 담아두기 보다는 auto_ptr 같은 스마트 포인터에 담아두면 스마트 포인터가 해제되는 시점에 알아서 자원을 delete 시킨다 .

std::auto_ptr<Class> pClass( createClass() )

→ 팩토리함수를 호출해서 auto_ptr 생성자의 매개변수로 넘김

** 위와 같이 자원을 획득하면서 동시에 auto_ptr 객체를 초기화 하는 것을 RAII( 자원 획득 즉시 초기화 ) 라 함

auto_ptr 이 가리키고 있는 객체는 동시에 한 놈밖에 존재할 수 없음 .( 복사 불가 )

pInv2 = pInv1 하면 pInv1 은 NULL 을 가리키게 됨std::tr1::shared_ptr<Class> 는 참조 카운팅 방식 스마트 포인터로 가리키는 포인터의 개수가 0 이 되면 알아서 자원을 삭제한다 .(gc 와 비슷하지만 두 포인터가 서로 가리키고 있다든지 할 수는 없다 .)

둘 다 내부에서는 delete 를 사용하므로 동적할당된 배열은 삭제하지 못한다 .

자원 관리에는 객체가 그만

Page 4: 이펙티브 C++ 공부

RAII 객체가 복사될 때의 처리를 잘 해야 한다 .(mutex 를 관리하는 lock 객체라면 복사할 것인가 ?)

1. 복사를 금지한다 . lock 객체같은 경우도 복사하는 것이 말이 안되는 경우가 많음 , un-

copyable 클래스를 상속하는 방법으로 복사방지를 하자 .

2. 자원에 대해 참조 카운팅을 수행 , tr1::shared_ptr 을 이용하자 . shared_ptr 에는 삭제자 지정 기능이 있어서 참조 카운트가 0 이 될 때 delete 대신 호출할 함수를 결정할 수 있다 .

3. 관리하고 있는 자원을 진짜 복사 , string 같은 경우는 실제로 복사함4. 자원의 소유권 이전 , 실제로 참조하는 RAII 객체가 딱 하나만 존재하게 하고 싶을때 ,

(auto_ptr)

자원 관리 클래스의 복사 동작에 대해 진지하게 고찰하자

Page 5: 이펙티브 C++ 공부

tr1::shared_ptr 같은 식으로 실제 포인터를 감싸고 있는 상황에서 , 실제 자원에 대한 포인터에 접근이 필요한 경우가 있다 . 이를 위한 두 가지 방법이 있다 .

명시적 접근 방법 : 스마트 포인터들은 get() 함수를 제공한다 . 이를 통해 명시적 접근이 가능하다 .

암시적 접근 방법 : pi1->isTaxFree() 같은 식으로 operator -> 가 알아서 실제 객체에 접근할 수 있도록 만드는 것이 가능함 . 가끔 의도치 않은 변환이 발생할 수도 있다 .

안전성 측면에서 명시적 방법이 나으나 , 암시적 방법이 편리할 때가 많다 .

자원 관리 클래스에서 관리되는 자원은 외부에서 접근할 수 있도록 하자

Page 6: 이펙티브 C++ 공부

std::string *stringArray = new std::string[100]

delete stringArray

문제는 new 는 [] 를 썼는데 delete 는 [] 를 쓰지 않았다는 것 .

new 와 delete 는 배열이면 배열 delete[] 로 단일 객체면 delete 로 짝을 맞춰 삭제해줘야함

이는 단일 객체와 객체의 배열이 사용하는 힙의 구조가 다르기 때문객체의 배열은 그 위치에 배열의 개수를 갖고 있고 , 단일 객체는 실제 그 object 를 갖고 있음

단일객체를 delete[] 로 지우면 object 를 숫자로 인식하여 그 개수만큼 지우기 시작함 (

미정의 동작 )

객체의 배열을 delete 로 지우면 한 개만 지워짐 , 나머지는 leak 발생** typedef 로 배열형태를 지정할 때는 조심하자 ! 지정된 형은 단일 객체이나 삭제는 [] 로 해야 함

new 및 delete 를 사용할 때는 형태를 반드시 맞추자

object

n object object

단일 객체

객체의 배열

Page 7: 이펙티브 C++ 공부

함수의 매개 인자로 스마트 포인터 객체를 생성해서 넘기는 것은 좋지 않다 .

processWidget(std::tr1::shared_ptr<Widget>(new Widget), priority() );

위와 같은 함수가 있다고 할 때 , new Widget 한 후 priority() 가 실행되는 과정에서 죽게 된다면 ,

tr1::shared_ptr 의 생성자까지 실행하지 못할 것이고 , 결국 포인터가 유실되기 때문 .

** C++ 컴파일러의 경우 컴파일러 별로 매개 인자에 대한 실행 순서가 달라질 수 있다 .

어떤 순서로 컴파일 될지는 보장할 수 없음 .

따라서 스마트 포인터 생성하는 부분을 별도의 문장으로 빼고 함수에 대입하는 것이 좋다 .

new 로 생성한 객체를 스마트 포인터에 저장하는 코드는 별도의 한 문장으로 ..

Page 8: 이펙티브 C++ 공부

설계 및 선언

Page 9: 이펙티브 C++ 공부

날짜를 대입하게 만드는 클래스라면 struct 을 이용해서 day, month 등을 명시하도록 하자 . 싱글톤을 사용하는 것도 좋다 .

일관성은 제대로 된 인터페이스를 만드는데 중요한 요소다 .

사용자 정의 타입은 기본 제공 타입처럼 동작하게 만들자 (operator 같은 것들을 ..)

사용자 쪽에서 뭔가 외워서 써야하는 것은 잘못 쓰기 쉽다 .

Investment* createInvestment(); 같은 함수는 포인터를 반환하는데 사용자쪽에서 직접 delete 를 해줘야 한다는 번거로움이 있다 . 이럴 땐 std::tr1::shared_ptr<Investment>

createInvestment(); 식으로 아싸리 shared_ptr 를 반환하게 만들자 .

shared_ptr 은 교차 DLL 문제를 피할 수 있다 . 알아서 생성된 DLL 의 delete 를 사용함** 교차 DLL : 생성 시에 A DLL 의 new 를 썼는데 지울 때 A` DLL 의 delete 로 지우는 경우

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

Page 10: 이펙티브 C++ 공부

새로 정의한 타입의 객체의 생성 및 소멸 방식은 생성자 소멸자 뿐 아니라 메모리 할당함수들의 설계까지 영향을 준다 .

객체 초기화와 객체 대입은 다르게

값에 의한 전달은 복사 생성자에서 구현한다 .

클래스 불변속성 http://en.wikipedia.org/wiki/Class_invariant

상속 가능한 클래스 인가 ? 어떤 함수에 vitrual keyword 를 써야하나

어떤 종류의 타입으로 변환 가능한가 ?

어떤 연산자와 함수를 넣을까 ?

멤버에 대한 접근권한은 어떻게 ? private, public, protected

사용자가 클래스를 사용할 때 보장받을 수 있는 것들은 어떤 것들로 할 것인가 ?

template 인가 ? 그냥 class 인가 ?

굳이 만들어야 하나 ?

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

Page 11: 이펙티브 C++ 공부

사용자 정의 클래스의 함수에서 값에 의한 전달을 하면 무의미한 생성자 , 소멸자 호출이 반복될 수 있다 . 이는 곧 성능 저하의 원인이 된다 .

상수 객체 참조자에 의한 전달 방식은 이러한 loss 를 줄여준다 .

bool validateStudent(Student s); 값에 의한 전달bool validateStudent(const Student& s); 상수객체 참조자에 의한 전달

이는 복사 손실 문제도 해결해준다 . 부모 , 자식 클래스가 있을 때 , 어떤 함수의 매개변수로 부모 객체가 값에 의해 전달되는 인터페이스를 사용하면 , 함수 사용시 자식 클래스를 넘겼을 때 복사손실 문제 (slicing problem) 가 생긴다 .

void printNameAndDisplay(Window w) 값에 의한 전달void printNameAndDisplay(const Window& w) 상수객체 참조자에 의한 전달

상수객체 참조자로 넘기면 문제없다 !!

복사하는 크기가 작다고 해서 값에 의한 전달을 사용하면 안 된다 . 일부 컴파일러는 레지스터에 포인터는 올려도 사용자 정의 타입은 안 올리기 때문 . ( 컴파일러에서 상수객체 참조자는 포인터로 구현 )

** 값에 의한 전달은 기본제공 타입 , STL 반복자 , 함수 객체 에만 사용하자

값에 의한 전달 보다는 상수객체 참조자에 의한 전달이 낫다 .

Page 12: 이펙티브 C++ 공부

참조자에 의한 반환을 하려고 하면 원본 대상이 있어야 한다 . 참조자는 원본의 다른 이름일 뿐이므로 . 따라서 원본을 만드는 순간 이미 new, delete 를 하게 되는 셈 , 그러니 그냥 값이 복사된 객체를 반환하자

원본을 local 영역에 만들 경우 , 함수가 종료되면서 stack 저장된 값이 사라지고원본을 heap 영역에 만들 경우 , 누군가 delete 해야 하며 , 이나마도 operator 가 두 번 이상 나오게 되면 하나만 delete 할 수 있으므로 메모리 누수 발생 .

static 으로 만들 경우 실제 이를 저장하고 있는 변수는 하나 뿐이므로 , (a*b) == (c*d) 같은 값 비교가 일어나면 무조건 true 를 반환할 수 밖에 없다

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

Page 13: 이펙티브 C++ 공부

데이터 멤버를 private 영역에 선언하면 , 문법적 일관성 , 접근 권한 설정 , 그리고 캡슐화 측면에서 유리하다 .

public 멤버 데이터 나 protected 멤버 데이터 나 캡슐화 측면에서 안 좋기는 동일하다 .

파생 클래스들에 영향을 미치므로 ..

캡슐화가 잘 되면 사용자에게 보여지지 않는 다른 부분들을 뜯어 고칠 수 있는 자유도가 많이 상승한다 .

데이터 멤버가 선언될 곳은 private 영역이다 .

Page 14: 이펙티브 C++ 공부

객체 안에서 모든 것을 해결한다고 객체지향적인 것은 아니다 .

같은 기능을 비멤버 ( 이면서 동시에 비 프렌드 ) 함수로 구현한다면 private 멤버에 접근할 수 있는 함수의 개수가 줄어듦으로 캡슐화는 더 좋아진다 .

** 다른 클래스의 멤버 함수인 것은 상관없다 !! 고로 utility class 를 따로 만들자 !! 편의 함수들을 utility class 에 넣자

utility class 를 다른 header 파일로 만들되 , 원래 클래스와 같은 네임스페이스 안에 두면 유용하게 써먹을 수 있다 .

또한 사용자가 같은 name space 를 만듦으로 쉽게 확장할 수 있다 .

** 이런 것들은 STL 에도 적용되어있음 std name space 를 쓰지만 헤더는 다 다르다 .

멤버 함수보다는 비멤버 , 비프렌드 함수와 더 가까워지자

Page 15: 이펙티브 C++ 공부

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

암시적 타입변환은 대체로 피해야 하지만 숫자 타입을 만들 때는 필요하다 .

사용자 정의 클래스와 기본 자료형 간의 연산은비멤버 비 프렌드 함수로 처리하는 것이 좋다 .

class Rational { ... };

const Rational operator* (const Rational& lhs, const Rational& rhs);

위와 같이 비 멤버 비 프렌드 함수로 만들면 사용할 때 , 기본 데이터 타입도 알아서 암시적 변환을 해주므로 동작에 일관성이 있으면서 제대로 동작도 한다 .

멤버 함수로 만들면 기본 자료형은 매개 변수가 아니므로2.operator*(oneHalf); 같이 이상한 호출이 되어버린다 .

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

Page 16: 이펙티브 C++ 공부

swap 은 STL 제공 함수 중 하나다 . 다만 값을 복사해서 사용하므로 포인터만 넘길 때는 비효율 적이다 . 따라서 성능 좋은 swap 을 만들어서 제공하는 것도 필요하다 .

먼저 멤버 함수로 swap 해주는 함수를 만든다 .

비멤버함수로 클래스와 같은 네임 스페이스에 template 으로 swap 함수를 만든다 . (

특수화 버전 )

** std 에는 트굿화 함수를 추가할 수 없다 . 만들어지고 컴파일도 되는데 결과가 미정의 동작을 한다 .

** 같은 네임스페이스에 넣으면 인자 기반 탐색에 의해 같은 네임 스페이스 함수부터 알아서 찾는다 .

swap 사용자는 특정 클래스에 대해 특수화된 swap 이 있는지 없는지 알 수가 없다 .

따라서 using std::swap 을 먼저 선언하고 swap 을 사용하면 없을 경우 알아서 std::swap

으로 실행해준다 .( 이렇게 swap 을 사용할 때는 한정자 std:: 을 붙여서는 안된다 .)

밖에서 호출하기 위해 멤버 함수로 만들어둔 swap 함수는 예외를 던져서는 안 된다 .

std::swap 은 강력한 안정성 보장을 제공하는 데 도움을 주는데 , 멤버 함수로 만들어 둔 swap 이 예외를 던지면 이게 망하기 때문 .

예외를 던지지 안는 swap 에 대한 지원도 생각해보자