13. 자원관리에는 객체가 그만이다.
- 자원누출을 막기위해, 생성자 안에서 자원을 획득하고 소멸자에서 그것을 해제하자.
RAII(Resource Acquisition is Initialize) 방식을 사용하자.
(RAII :생성자에서 할당하고, 에러 상황에서도 호출이 보장되는 같은 객체의 소멸자 같은 것에서 리소스를 해제)
- 일반적인 RAII클래스는 tr1::shared_ptr과 auto_prt이다.
(둘 중 tr1:shared_ptr이 복사시의 동작이 직관적이기 때문에 좋다.)
(auto_ptr은 복사되는 객체(원본 객체)를 null로 만들어 버린다.)
14. 자원관리 클래스의 복사 동작에 대해 진지하게 고찰하자.
- RAII객체의 복사는 그 객체가 관리하는 자원의 복사 문제를 안고가고 때문에 그 자원을 어떻게 복사하느냐에 따라 RAII객체의
복사 동작이 결정된다.
- RAII클래스에 구현하는 일반적인 복사동작은 복사를 금지하거나 참조 카운팅을 해주는 선으로 마무리한다.
15. 자원관리 클래스에서 관리되는 자원은 외부에서 접근할 수 있도록 하자.
- 실제 자원을 직접 접근해야 하는 기존 API가 많으므로 RAII클래스를 만들때는 그 클래스가 관리하는 자원을 얻을 수 있는 방법
을 만들자.
- 자원 접근은 명시적 변환 혹은 암시적 변환을 통해 가능하다.
(명시적변환은 안전성에서 우수하고, 고객편의성을 놓고보면 암시적변환이 우수하다.)
16. new 및 delete를 사용할 때는 형태를 반드시 맞추도록 하자.
- new 표현식에 []가 있으면, delete표현식에도 []를 써야 한다.
- new 표현식에 []가 없으면 delete표현식에도 []를 사용해서는 안된다.
17. new로 생성한 객체를 스마트포인터에 저장하는 코드는 별도의 한 문장으로 만들자.
- 예외가 발생할 때 디버깅을 하기 힘든 자원 누출이 초래될 수 있다.
어떤 함수가 있고 동적으로 할당한 객체에 스마트포인터를 사용할 시
int process();
void foo(std::tr1::shared_ptr<ABC>(new ABC), process);
위 소스는 컴파일은 가능하나 C++컴파일러는 함수 호출시 이루어지는 연산의 순서를 정하는데 있어서 상당한 자유도를 갖고 있
으므로 (JAVA나 C#은 매개변수의 평가순서가 특정하게 고정) 컴파일러에 따라 자원 누출을 초래하는 결과가 생길 수도 있다.
// 해결책
std::tr1::shared_ptr<ABC> pw(new ABC);
foo(pw, process());
이렇게 하면 문장과 문장 사이에 있는 연산들이 컴파일러의 재조정을 받을 여지가 적어지므로 자원 누출 가능성이 없어진다.
18. 인터페이스 설계는 제대로 쓰기엔 쉽게, 엉터리로 쓰기엔 어렵게 하자.
- 좋은 인터페이스 자체는 제대로 쓰기 쉽게, 엉터리로 쓰기에 어렵게 되어 있다.
- 사용자의 실수를 방지하도록 새로운 타입을 만들거나, 타입에 대한 연산을 제한하기, 객체의 값에
대해 제약걸기, 자원관리 작업을 사용자 책임으로 놓지 않기 등이 있다.
- tr1::shared_ptr은 사용자 정의 삭제자를 지원한다. 이 특징 때문에 교차 DLL 문제를 막아주며, 뮤
텍스등을 자동으로 잠금 해제하는데 쓸 수 있다.
* 교차 DLL(Cross-DLL) : 객체 생성시 dll의 new를 썻는데 그 객체를 삭제할 때는 이전의 dll과 다
른 dll에 있는 delete를 썻을 경우 발생한다.
19. 클래스 설계는 타입 설계와 똑같이 취급하자.
- 새로 정의한 타입에 객체 생성 소멸은 어떻게 할 것인가?
- 객체 초기화는 객체 대입과 어떻게 달라야 하는가?
- 새로운 타입으로 만든 객체가 값에 의한 전달일 경우 어떤 의미인가?
- 새로운 타입이 가질 수 있는 적법한 값에 대한 제약은 무엇으로 할 것인가?
- 기존의 클래스 상속 계통망에 맞출 것인가?
- 어떤 종류의 타입 변환을 허용할 것인가?
- 어떤 연산자와 함수를 두어야 의미가 있는가?
- 표준 함수중 어떤 것을 허용하지 말아야하는가(private member function)?
- 멤버에 대한 접근 권한을 어느 쪽에 줄 것인가?
- 선언되지 않은 인터페이스로 무엇을 둘 것인가?
- 새로 만드는 타입이 얼마나 일반 적인가?
- 정말로 꼭 필요한 타입인가?
빠짐 없이 점검해보도록 하자.
20. '값에 의한 전달'보다는 '상수 객체 참조자에 의한 전달'방식을 사용하자.
- 대체 적으로 효율 적일 뿐만 아니라(생성자, 소멸자가 호출되지 않음) 복사 손실 문제도 막아준다.
- 단, 기본 제공 타입(int등), STL iterator, 펑터(함수 객체)에는 맞지 않다.
- 이들은 값에 의한 전달이 더 적절하다.
21. 함수에서 객체를 반환해야 할 경우에 참조자를 반환하려고 들지 말자.
- 함수 수준에서 새로운 객체를 만드는 방법은 두 가지 이다.(스택과 힙)
- 지역 스택 객체에 대한 포인터나 참조자를 반환하는 일.
- 힙에 할당된 객체에 대한 참조자를 반환하는 일.
- 지역 정적 객체에 대한 포인터나 참조자를 반환하는 일.
- 그런 객체가 두 개 이상 필요해질 가능성이 있다면 하지 말도록 하자.
22. 데이터 멤버가 선언될 곳은 private 임을 명심하자.
- 데이터 멤버는 private로 선언하고 문법적으로 일관성 있는 데이터 접근 통로를 제공해주자.
- protected는 public 보다 더 많이 보호 받고 있는 것이 아니라는 것을 명심하자.
23. 멤버 함수보다는 비 멤버 비프랜드 함수와 더 가까워지자.
- 관련 기능을 구성하는데 있어서 패키지 유연성(package flexibility)가 높아진다.
- 컴파일 의존도를 낮추며 함수의 확장성도 높일 수 있다.
- 클래스 내부에서 멤버 함수를 정의하는 것도 좋지만
void foo(Student& Stu)
{
stu.clearHistory();
stu.clearList();
}
식으로 사용하는 것도 좋다는 것이다.
24. 타입 변환이 모든 매개 변수에 대해 적용되어야 한다면 비 멤버 함수를 선언하자.
- 어떤 함수에 들어가는 모든 매개 변수(this 포인터가 가리키는 객체 포함)에 대해 타입 변환이 필요하다면
그 멤버 함수는 비멤버이어야 한다.
25. 예외를 던지지 않는 swap에 대한 지원도 생각해보자.
- std::swap이 타입에 대해 느리게 동작할 여지가 있다면 swap 멤버 함수를 제공하자(단 예외는 던지지 않게 만들 것)
- 멤버 swap을 제공 했으면, 이 멤버를 호출하는 비멤버 swap도 제공하자.
- 클래스(템플릿이 아닌)에 대해서는 std::wap을 만들도록 하자.
- 사용자 입장에서 swap을 호출할 때는 std::swap에 대한 using 선언을 넣어준 후에 네임 스페이스 한정없이 swap을 호출하자.
- 사용자 정의 타입에 대한 std 템플릿을 완전 특수화 하는 것이 가능하지만 std에 어떤 것이라도 새로 추가하려고 들지는 말자.
- 1. 두 객체의 값을 맞바꾸는 함수를 swap이라 하고, public 멤버 함수로 둔다.
- 2. 클래스나 템플릿이 들어 있는 네임 스페이스와 같은 곳에 비멤버 swap을 만든다. 그리고 1번에
서 만든 swap 멤버 함수를 이 비멤버 함수가 호출하도록 한다.
- 3. 새로운 클래스를 만든다면 그 클래스에 대한 std::swaap의 특수화 버전을 준비하자.
이 버전에서도 swap 멤버 함수를 호출하도록 하자.
template<typename T>
{
using std::swap; // std::swap을 함수 안으로 끌어옴.
...
swap(obj1, obj2); // T타입의 전용 SWAP을 호출.
}
'Basic Programming > C, C++' 카테고리의 다른 글
C++ - Effectvice C++의 55가지 테크닉 Part - 4 (0) | 2016.03.04 |
---|---|
C++ - Effectvice C++의 55가지 테크닉 Part - 3 (0) | 2016.03.04 |
C++ - Effectvice C++의 55가지 테크닉 Part - 1 (0) | 2016.03.03 |
C++ - ++i 와 i++의 차이 (0) | 2016.03.03 |
C++ - #pragma (0) | 2016.03.03 |