3. 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와 비슷하지만 두 포인터가 서로 가리키고 있다든지 할 수
는 없다.)
자원 관리에는 객체가 그만
4. RAII 객체가 복사될 때의 처리를 잘 해야 한다.(mutex 를 관리하는 lock객체라면 복사할 것인
가?)
1. 복사를 금지한다. lock객체같은 경우도 복사하는 것이 말이 안되는 경우가 많음,
uncopyable 클래스를 상속하는 방법으로 복사방지를 하자.
2. 자원에 대해 참조 카운팅을 수행, tr1::shared_ptr을 이용하자. shared_ptr에는 삭제자 지정
기능이 있어서 참조 카운트가 0이 될 때 delete대신 호출할 함수를 결정할 수 있다.
3. 관리하고 있는 자원을 진짜 복사, string같은 경우는 실제로 복사함
4. 자원의 소유권 이전, 실제로 참조하는 RAII객체가 딱 하나만 존재하게 하고 싶을
때,(auto_ptr)
자원 관리 클래스의 복사 동작에 대해 진지하게 고찰하자
5. tr1::shared_ptr같은 식으로 실제 포인터를 감싸고 있는 상황에서, 실제 자원에 대한 포인터에
접근이 필요한 경우가 있다. 이를 위한 두 가지 방법이 있다.
명시적 접근 방법 : 스마트 포인터들은 get()함수를 제공한다. 이를 통해 명시적 접근이 가능하
다.
암시적 접근 방법 : pi1->isTaxFree() 같은 식으로 operator -> 가 알아서 실제 객체에 접근할 수
있도록 만드는 것이 가능함. 가끔 의도치 않은 변환이 발생할 수도 있다.
안전성 측면에서 명시적 방법이 나으나, 암시적 방법이 편리할 때가 많다.
자원 관리 클래스에서 관리되는 자원은 외부에서 접근할 수 있도록 하자
6. 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
단일 객체
객체의 배열
7. 함수의 매개 인자로 스마트 포인터 객체를 생성해서 넘기는 것은 좋지 않다.
processWidget(std::tr1::shared_ptr<Widget>(new Widget), priority() );
위와 같은 함수가 있다고 할 때, new Widget 한 후 priority()가 실행되는 과정에서 죽게 된다면,
tr1::shared_ptr의 생성자까지 실행하지 못할 것이고, 결국 포인터가 유실되기 때문.
** C++ 컴파일러의 경우 컴파일러 별로 매개 인자에 대한 실행 순서가 달라질 수 있다. 어떤 순
서로 컴파일 될지는 보장할 수 없음.
따라서 스마트 포인터 생성하는 부분을 별도의 문장으로 빼고 함수에 대입하는 것이 좋다.
new로 생성한 객체를 스마트 포인터에 저장하는 코드는 별도의 한 문장으
로..
9. 날짜를 대입하게 만드는 클래스라면 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로 지우는 경우
인터페이스 설계는 제대로 쓰기엔 쉽게 엉터리로 쓰기엔 어렵게 하자
10. 새로 정의한 타입의 객체의 생성 및 소멸 방식은 생성자 소멸자 뿐 아니라 메모리 할당함수들
의 설계까지 영향을 준다.
객체 초기화와 객체 대입은 다르게
값에 의한 전달은 복사 생성자에서 구현한다.
클래스 불변속성 http://en.wikipedia.org/wiki/Class_invariant
상속 가능한 클래스 인가? 어떤 함수에 vitrual keyword 를 써야하나
어떤 종류의 타입으로 변환 가능한가?
어떤 연산자와 함수를 넣을까?
멤버에 대한 접근권한은 어떻게? private, public, protected
사용자가 클래스를 사용할 때 보장받을 수 있는 것들은 어떤 것들로 할 것인가?
template인가? 그냥 class인가?
굳이 만들어야 하나?
클래스 설계는 타입 설계와 똑같이 취급하자
11. 사용자 정의 클래스의 함수에서 값에 의한 전달을 하면 무의미한 생성자, 소멸자 호출이 반복
될 수 있다. 이는 곧 성능 저하의 원인이 된다.
상수 객체 참조자에 의한 전달 방식은 이러한 loss를 줄여준다.
bool validateStudent(Student s); 값에 의한 전달
bool validateStudent(const Student& s); 상수객체 참조자에 의한 전달
이는 복사 손실 문제도 해결해준다. 부모, 자식 클래스가 있을 때, 어떤 함수의 매개변수로 부
모 객체가 값에 의해 전달되는 인터페이스를 사용하면, 함수 사용시 자식 클래스를 넘겼을 때
복사손실 문제(slicing problem)가 생긴다.
void printNameAndDisplay(Window w) 값에 의한 전달
void printNameAndDisplay(const Window& w) 상수객체 참조자에 의한 전달
상수객체 참조자로 넘기면 문제없다!!
복사하는 크기가 작다고 해서 값에 의한 전달을 사용하면 안 된다. 일부 컴파일러는 레지스터
에 포인터는 올려도 사용자 정의 타입은 안 올리기 때문. (컴파일러에서 상수객체 참조자는 포
인터로 구현)
** 값에 의한 전달은 기본제공 타입, STL 반복자, 함수 객체 에만 사용하자
값에 의한 전달 보다는 상수객체 참조자에 의한 전달이 낫다.
12. 참조자에 의한 반환을 하려고 하면 원본 대상이 있어야 한다. 참조자는 원본의 다른 이름일 뿐
이므로. 따라서 원본을 만드는 순간 이미 new, delete를 하게 되는 셈, 그러니 그냥 값이 복사된
객체를 반환하자
원본을 local 영역에 만들 경우, 함수가 종료되면서 stack 저장된 값이 사라지고
원본을 heap 영역에 만들 경우, 누군가 delete해야 하며, 이나마도 operator가 두 번 이상 나오
게 되면 하나만 delete할 수 있으므로 메모리 누수 발생.
static으로 만들 경우 실제 이를 저장하고 있는 변수는 하나 뿐이므로, (a*b) == (c*d) 같은 값 비
교가 일어나면 무조건 true를 반환할 수 밖에 없다
함수에서 객체를 반환해야 할 경우에 참조자를 반환하려 들지 말자
13. 데이터 멤버를 private 영역에 선언하면, 문법적 일관성, 접근 권한 설정, 그리고 캡슐화 측면에
서 유리하다.
public 멤버 데이터 나 protected 멤버 데이터 나 캡슐화 측면에서 안 좋기는 동일하다.
파생 클래스들에 영향을 미치므로..
캡슐화가 잘 되면 사용자에게 보여지지 않는 다른 부분들을 뜯어 고칠 수 있는 자유도가 많이
상승한다.
데이터 멤버가 선언될 곳은 private 영역이다.
14. 객체 안에서 모든 것을 해결한다고 객체지향적인 것은 아니다.
같은 기능을 비멤버(이면서 동시에 비 프렌드) 함수로 구현한다면 private 멤버에 접근할 수 있
는 함수의 개수가 줄어듦으로 캡슐화는 더 좋아진다.
** 다른 클래스의 멤버 함수인 것은 상관없다!! 고로 utility class를 따로 만들자!! 편의 함수들을
utility class에 넣자
utility class를 다른 header 파일로 만들되, 원래 클래스와 같은 네임스페이스 안에 두면 유용하
게 써먹을 수 있다.
또한 사용자가 같은 name space를 만듦으로 쉽게 확장할 수 있다.
** 이런 것들은 STL 에도 적용되어있음 std name space를 쓰지만 헤더는 다 다르다.
멤버 함수보다는 비멤버, 비프렌드 함수와 더 가까워지자
15. 어떤 함수에 들어가는 모든 매개변수에 대해서 타입 변환을 해 줄 필요가 있다면 그 함수는 비
멤버여야 한다.
암시적 타입변환은 대체로 피해야 하지만 숫자 타입을 만들 때는 필요하다.
사용자 정의 클래스와 기본 자료형 간의 연산은
비멤버 비 프렌드 함수로 처리하는 것이 좋다.
class Rational { ... };
const Rational operator* (const Rational& lhs, const Rational& rhs);
위와 같이 비 멤버 비 프렌드 함수로 만들면 사용할 때, 기본 데이터 타입도 알아서 암시적 변
환을 해주므로 동작에 일관성이 있으면서 제대로 동작도 한다.
멤버 함수로 만들면 기본 자료형은 매개 변수가 아니므로
2.operator*(oneHalf); 같이 이상한 호출이 되어버린다.
타입 변환이 모든 매개변수에 대해 적용되어야 한다면 비멤버 함수를 선언
하자
16. swap은 STL 제공 함수 중 하나다. 다만 값을 복사해서 사용하므로 포인터만 넘길 때는 비효율
적이다. 따라서 성능 좋은 swap을 만들어서 제공하는 것도 필요하다.
먼저 멤버 함수로 swap해주는 함수를 만든다.
비멤버함수로 클래스와 같은 네임 스페이스에 template으로 swap함수를 만든다. (특수화 버전)
** std에는 트굿화 함수를 추가할 수 없다. 만들어지고 컴파일도 되는데 결과가 미정의 동작을
한다.
** 같은 네임스페이스에 넣으면 인자 기반 탐색에 의해 같은 네임 스페이스 함수부터 알아서 찾
는다.
swap 사용자는 특정 클래스에 대해 특수화된 swap이 있는지 없는지 알 수가 없다.
따라서 using std::swap을 먼저 선언하고 swap을 사용하면 없을 경우 알아서 std::swap으로 실
행해준다.(이렇게 swap을 사용할 때는 한정자 std::을 붙여서는 안된다.)
밖에서 호출하기 위해 멤버 함수로 만들어둔 swap 함수는 예외를 던져서는 안 된다. std::swap
은 강력한 안정성 보장을 제공하는 데 도움을 주는데, 멤버 함수로 만들어 둔 swap이 예외를
던지면 이게 망하기 때문.
예외를 던지지 안는 swap에 대한 지원도 생각해보자