항목 13 : 자원 관리는 객체로 하자
동적 할당된 메모나, 뮤텍스 잠그기등의 여러 자원들은 할당을 받은 뒤 사용을 끝내고 반드시 해제를 해야한다. 이러한 기능을 우리가 사용하거나 남이 사용하도록 인터페이스를 만들어줄 때는 사용자가 직접 반납을 하도록 하는 것이 아니라 이 자원을 관리하는 객체를 두고 그 객체의 소멸자가 자원을 해제하도록 하는 것이 좋다.대표적인 예시가 스마트 포인터이다. unique_ptr이나 shared_ptr등으로 동적할당된 메모리 또는 포인터를 관리하자. 만약 자신이 동적할당으로 만든 자원의 포인터를 리턴해주는 함수를 만들었다면 로우 포인터가 아닌 스마트포인터에 담아 리턴해주자.이러한 초기화시 자원을 획득하고(받거나 생성) 소멸 시 자원을 해제하는 기법을 RAII라 하며 이러한 객체를 RAII객체라 한다.
이러한 RAII 객체는 진짜로 자원 관리만을 목적으로 가지는 스마트 포인터도 될 수 있으며 스스로 의미를 가지며 사용되면서도 자신의 자원을 관리하는 객체도 될 수 있다.
항목 14 : 자원 관리 객체의 복사는 신중히 만들자
RAII객체의 경우 그 객체의 용도와 사용에 따라 적절한 복사를 구현해야한다. RAII는 단순히 자신의 비트자원외에 추가적인 논리적 자원을 소유한 경우가 많기에 해당 객체에서의 복사가 가지는 의미를 정확히 생각하고 구현하자. 대표적 복사 구현의 예시는 다음과 같다.
복사 금지
unique_ptr는 이동만 허용하고 복사를 아예 금지시킨다.
관리 하고 있는 자원에 대하여 카운팅
shared_ptr은 현재 자원을 참조중인 포인터를 카운팅한다.
깊은 복사
동적 할당 자원의 포인터를 가진 정보 객체를 복사하면 그 포인터가 가르키고 있는 자원까지 통채로 깊은 복사하여 복사한다.
항목 15 : 자원 관리 목적 클래스에서 관리하는 자원은 외부에 대한 인터페이스를 제공하자.
말그대로다. 만약 자신이 스마트포인터를 만들었다면 당연히 스마트포인터가 가진 포인터를 실제로 제공할 방법을 만들어줘야 할 것이다. 이는 여러 방법이 있는데 다음과 같다.
1. get메소드 구현
2. 형변환 구현
여기서 형변환은 명시,암시적 두가지가 있는데 암시적 형변환 같은 경우는 사용시 편리함은 늘려주지만 실수를 유발 할 수 있기에 제공을 할지 안할지 고민해봐야한다.
항목 16 : new와 delete에서 []유무는 반드시 맞추자
new type으로 할당된 메모리는 그 타입 하나에 대한 메모리를 가진다. 그에 반해 new type[n]으로 할당된 메모리는 그 배열의 크기에 대한 데이터와 배열에 대한 메모리를 가지게 된다. 이러한 크기가 메모리에 어떤식으로 저장되는지는 컴파일러마다 다르지만 new type의 할당과 다른 메모리형태를 가지게 됨은 분명하다.
delete a는 a를 그 타입 하나만 할당된 메모리의 포인터로 간주하고 이를 할당 해제한다. delete a[]는 a를 배열의 크기와 배열이 담긴 메모리의 포인터로 간주하고 이를 할당 해제한다. 즉 각 할당에 대해 적절한 delete를 하지않으면 단순히 메모리 누수이상의 문제가 발생할 수 있기에 이를 반드시 피해야한다.
추가로 이러한 이유로 typedef로 타입 별칭을 제공할땐 해당 별칭이 타입 하나를 나타내는지 타입의 배열을 나타내는지 반드시 문서화할 필요가 있다.
항목 17 : new로 생성한 포인터로 스마트 포인터를 생성할 땐 딱 그 문장만 써라
다음과 같은 상황을 보자
void fff(shared_ptr<a> pt, int i)
{
...
}
int fff2() //예외 발생 가능
{
...
}
fff(shared_ptr<a>(new a), fff2() ); //호출
위에서 fff를 호출하는 문장은 위험성을 가지고 있다. 왜냐하면 c++표준에서는 함수 호출 시 각 매개변수의 표현식을 수행하는 순서는 정해져있지 않기 때문이다. 물론 위 함수에서 new a가 shared_ptr의 생성자보다 먼저 실행되는것은 보장되지만 fff2의 호출 순서는 언제일지 보장할 수 없다. 즉 다음과 같은 순서로 호출 될 수 있다는 거다.
1. new a
2. fff2()
3. shared_ptr 생성자
이때 fff2()에서 예외가 발생하면 a를 생성했음에도 스마트포인터에 들어가지 못해 메모리 누수가 발생한다.
따라서 이를 다음과 같이 수정해야한다.
shared_ptr<a> pt = shared_ptr<a>(new a)
fff(pt, fff2() );
그래서 결론은 메모리 할당과 스마트포인터 생성은 별도의 문장으로 구현하는 것이 좋다는 것이다. 꼭 매개변수가 아니더라도 해당 구문을 다른 문장 속에 섞어 넣으면 예상치못한 순서에 의해 메모리 할당만 일어나고 스마트포인터 생성이 안될 수 있으므로 그냥 별도의 문장으로 만드는 습관을 들이자.
참고서적
Effective C++
'프로그래밍 언어 > Effective C++' 카테고리의 다른 글
[Effective C++]항목 23~25 : 클래스에 대한 비멤버 함수 (0) | 2023.04.10 |
---|---|
[Effective C++]항목 18~22 : 클래스 설계 기초 (0) | 2023.04.10 |
[Effective C++]항목 9~12 : 생성자, 소멸자 및 대입 연산자 2 (0) | 2023.03.25 |
[Effective C++]항목 5~8 : 생성자, 소멸자 및 대입 연산자 1 (0) | 2023.03.24 |
[Effective C++]항목 1~4 : C++ 기초 사고방식 (0) | 2023.03.22 |