자원 관리에는 객체가 그만!


class Investment { ... }  --> Base 클래스


Investment* createInvestment();

--> Drived 클래스들의 객체를 동적 할당하고

      그 포인터를 반환하는 팩토리 함수.


이 객체의 해제는 호출자 쪽에서 직접 해야 한다.


void f(){

investment *pInv = createInvestment();

....

....

delete pInv;

}


언뜻 보기엔 별 문제 없어 보인다.

1. 하지만 ... 부분에서 return; 이 있을 경우

2. continue, goto가 있을 경우

3. 예외가 발생될 경우

위의 경우 delete는 실행 되지 않고

메모리가 누출되고 자원이 새게 됩니다.




해결책

createInvestment() 함수로 만들어낸 자원이

항상 해제되도록 만드는 방법은

자원을 객체에 넣고 그 자원 해제를

소멸자가 맡도록 하며, 그 소멸자는 실행 제어가

f를 떠날 때 호출 되도록 만드는 것이다.




개발에 쓰이는 상당수의 자원이 힙에서 동적으로

할당되고, 하나의 블록 혹은 함수 안에서만 쓰이는

경우가 잦기 때문에 그 블록 혹은 함수로부터

실행 제어가 빠져 나올 때 자원이 해제되는 것이 맞다.




스마트 포인터 auto_ptr을 써보자.

가리키고 있는 대상에 대해 소멸자가

자동으로 delete를 불러주도록 설계되어 있다.


void f(){

auto_ptr<Investment> pInv (createInvestment());

...

...

}

--> 블럭이 끝나면 auto_ptr의 소멸자를 통해 pInv를 삭제한다.


위와 같이 자원 관리 객체를 사용하는 

방법의 중요한 특징에 대해 알아보자.




1. 자원을 획득한 후에 자원 관리 객체에 넘긴다.

createInvestment() 함수가 만들어준 자원을

auto_ptr 객체를 초기화 하는 데 쓰이고 있다.

--> 자원 획득 초기화 (RAII)

자원 획득과 자원 관리 객체의 초기화가 

한 문장에서 이루어지는 것이 일반적이다.


2. 자원 관리 객체는 자신의 소멸자를 사용해

자원이 확실히 해제되도록 해야한다.

소멸자는 객체가 소멸될 때 자동적으로

호출되기 때문에, 실행 제어가 어떤 경위로

블록을 떠나가는가에 상관없이 자원 해제가

제대로 이루어지게 되는 것이다.




주의점!!

auto_ptr은 자신이 소멸될 때, 자신이 가리키는

객체를 자동으로 delete를 해주게 된다.

따라서 같은 객체를 두개의 auto_ptr이

가리키고 있다면 자원이 두번 삭제되는 결과.


---> 때문에 auto_ptr은 객체를 복사하게 되면

원본객체는 null로 만들어버린다.

예제를 보도록 하자.


auto_ptr<Investment> pInv1(createInvestment());

auto_ptr<Investment> pInv2(pInv1);

--> pInv2가 객체를 가리켜 pInv1은 null

pInv1 = pInv2;

--> 이제는 pInv2가 null


문제점 : STL 컨테이너의 경우엔 원소들의 정상적인 복사 동작을

가져야 하기 때문에 auto_ptr은 최선의 방법이 아니다.


해결책

참조 카운팅 방식 스마트 포인터 (RCSP)를 쓰자!

참조 카운팅은 여러개의 객체(자원 관리 객체)들이 동일한 값을 가질 때,

그 객체들로 하여금 그 값을 나타내는 하나의 데이터를

공유하게 하여 데이터의 양을 절약하는 기법이다.


어떠한 자원을 참조하는 횟수를 기록하여 자원의 생명주기를 관리한다.

이 횟수가 있다는 것은 메모리 어딘가에서 쓰이고 있다는 것이다.

함부로 자원을 삭제하면 사용 중인 정보가 무효화 될 것이다.

따라서 참조 횟수가 0이 되기 전에는 이 자원이 삭제되지 않는다.


TR!에서 제공하는 std::tr1::shared_ptr이 대표적인 RCSP이다.


void f(){

shared_ptr<Investment> pInv1 (createInvestment());

shared_ptr<INvestment> pInv2(pInv);

pInv1 = pInv2;

}


--> 복사와 대입 문제없이 실행된다.


주의점!

스마트 포인터의 소멸자에서는 delete 연산자를

사용하고 있다는 것이다. delete[]가 아니라는 점!

따라서 동적 할당된 배열에 대해 스마트 포인터를

사용하지 말자는 것. 동적 할당된 배열은 vector이나

string을 통해 거의 대체할 수 있기 때문이다.



POINT!!

1. 자원 누출을 막기 위해, 생성자 안에서 자원을 획득하고

소멸자에서 그것을 해제하는 RAII객체를 사용하자.


2. 널리 쓰이는 RAII는 대표적으로 auto_ptr, shared_ptr이 있다.


그리디 알고리즘01 회의실 배정 (정올)


처음으로 100점이 나왔다.

정말 쉬운 문제이긴 하지만

답을 보지 않고 처음부터 끝까지

내가 푼 문제라 더 뿌듯하다.







물론 최적화된 코드는 절대 아니다.


1. 일단 종료시간을 기준으로 오름차순으로 정렬

2. 정렬된 후에 맨 앞에 있는 회의는 무조건 맨 처음에 들어갈 수 있다.

따라서 FindAnswer(0) 으로 함수를 호출한다.

3. if(0번 째 회의라면) 넌 답이니까 v에 들어가라.


4. 그리고 다음 회의를 찾아간다.

for문으로 자신의 오른쪽 방향으로 하나씩 탐색.


맨 처음에 들어갈 회의가 결정났기 때문에

맨 처음 회의의 종료시간과 그 다음 회의의

시작시간을 비교 if(종료시간 <= 다음 놈 시작시간)


5. 해당하는 회의를 찾았다면 그 회의의 num을

v에다가 집어 넣고, 그 회의의 인덱스로

다시 FindAnswer(index) 함수 호출.

그리고 본 함수는 종료시켜준다. return;


6. 재귀를 통해 답을 찾다가

해당하는 회의가 없어 for문 루프를 빠져나오면

함수를 종료 시킨다. return;




재귀 함수


재귀 함수의 기본적 이해

-> 자기 자신을 다시 호출하는 형태의 함수


재귀를 공부해야 하는 이유

자료구조나 알고리즘을 공부할 때

재귀 함수가 유용하게 사용되어 진다.




탈출 조건의 필요성

 - 무한 재귀 호출을 피하기 위해서 (무한 루프)

 - 무한 재귀 호출 -> stack overflow


 탈출 조건의 이해

void Re(int n){

...

if(n==1)

return;

Re(n-1);

}

int main(){

int a= 5;

Re(5);

}


--> return은 값의 반환 뿐만 아니라

      함수를 빠져나올 때 쓸 수 있다.




재귀 함수 Design

- 팩토리얼 계산을 위한 알고리즘


int factorial(int n){

if(n==1) return 1;

else return n * factorial(n-1);

}




재귀함수의 효율성

속도나 메모리의 절약을 의미하는 것이 아니다.

오히려 잦은 함수 호출로 인한 속도 저하와 

stack overflow, 즉 다룰 수 있는 범위를

초과하는 문제가 발생 할 수 있다.


논리적 사고를 그대로 옮기거나

코드를 단순화 하기에 적합하다!


+ Recent posts