백트래킹 알고리즘 N Queen (정올)


이 문제를 푸는데 10시간은 걸린 듯하다.

어떤 식으로 풀어야 한다는 것은 머리에 그려졌지만

대각선 방향으로의 체크를 어떤 식으로 진행해야 할지

방법이 떠오르지 않았다. 내가 수학문제를 푸는지

프로그래밍을 하고 있는지 모르겠다. 맘에 안든다.






01. 문제 개요



02. 선언



03. 체크 방법




04. 답 찾기



06. 메인



제발 이런 문제는 테스트에 나오지 않길..


데이터 멤버가 선언될 곳은 private 영역임을 명심하자.




1. 문법적 일관성

데이터 멤버가 public이 아니라면 접근은

함수로 해야할 것이고, 공개 인터페이스에 있는 것들이

전부 함수뿐이라면, 그 클래스에 멤버에 접근하고 싶을 때

괄호를 붙여야 하는지 말아야 하는지 고민할 필요가 없다.




2. 데이터 멤버의 접근성에 대해 훨씬 정교한 제어를 할 수 있다.

멤버 함수를 통한 접근은 접근 불가, 읽기 전용, 읽기 쓰기 접근을

내가 직접 구현할 수 있다. 심지어는 쓰기 전용 접근까지도




3. 캡슐화

자동차가 지나가는 속도를 모니터링 하는 클래스가 있다.

class speedData{

...

public:

void Add(int speed);

double average() const;

...

}


평균 값을 구하는 average 함수를 구현하는 방법은 두 가지이다.

첫 번째, 평균값을 넣어두는 데이터 멤버를 반환하는 방법

두 번째, 함수가 호출될 때 마다 평균값을 계산해서 반환하는 방법

이렇듯 평균값 접근에 멤버 함수를 통해 하게되면

(평균값을 캡슐화 하게되면) 내부 구현을 이렇게 혹은 저렇게

바꿀 수 있게 되고, 사용자는 기껏 해봤자 컴파일만 다시하면 된다.




4. 구현상의 융통성을 전부 누릴 수 있다.

데이터 멤버를 읽거나 쓸 때 다른 객체에 알림을

보낸다든지, 클래스의 불변속성 및 사전조건, 사후조건을

검증한다든지, 스레딩 환경에서 동기화를 건다든지 하는 일이다.

(C# 에서는 프로퍼티라 한다.)




5. 불변속성

사용자로부터 데이터 멤버를 숨기면, 클래스의 불변속성을

항상 유지하는 데 절대로 소홀해질 수 없게 된다. 불변속성을

보여줄 수 있는 통로가 멤버 함수밖에 없기 때문이다.


캡슐화는 현재의 구현을 나중에 바꿀 수 있다. --> 3번 참고

데이터 멤버가 public으로 되어있다면 캡슐화되지 않았다는 뜻이고

현재의 구현을 나중에 바꾸게 되면 코드가 깨지게 된다.


public으로 데이터 멤버를 선언할 경우 이 데이터 멤버를

변경하고자 할 때, 이 데이터를 쓰고 있는 모든 부분을

수정해야 할 것이며, protected 데이터 멤버라고 해도

상속받는 모든 클래스의 코드가 깨지게 될 것이다.




POINT!!

1. 데이터 멤버는 private로 선언하자,

2. 접근 수준은 private(캡슐화 제공) 그리고 나머지 (캡슐화 없음)으로 구분하자.

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


유리수를 나타내는 클래스가 있고 이 클래스에는

두 유리수를 곱하는 멤버 함수가 선언되어 있다.


Rational {

private:

int n ,d;

public:

Rational(int _n, int _d);

...

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

};




Rational operator*(..); 위 함수에서는

참조자가 아닌 객체를 반환하고 있다.

이 함수가 참조자를 반환하도록 만들어졌다면,

이 함수가 반환하는 참조자는 반드시 이미 존재하는

Rational 객체의 참조자여야 한다.


참조자를 반환한다면 객체의 생성과 소멸에 드는

비용을 조금이라도 줄여지지 않을까?

그렇다면 참조자를 반환해보자.


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

Rational result (lhs.n * hrs.n, lhs.d * rhs.d );

return result;

}




참조자를 반환하려면 그 객체가 존재해야 한다고 말했다.

따라서 어차피 함수 안에서 객체가 하나 만들어진다.

즉, 객체의 생성과 소멸에 드는 비용을 지불한다는 말이다.


이보다 더 큰 문제는 result는 지역 객체이다.

함수가 끝나면 이 객체는 소멸된다.

따라서 이 함수는 소멸된 객체의 참조자를

리턴하고 있는 것이다. 이 참조자가

가리키고 있는 놈은 존재하지 않는다.




그렇다면 스택이 아닌 힙에 생성하고 참조자를 반환하면 어떤가?

const Rational& operator*(const Rational% lhs, const Rational& rhs){

Rational *result = new Rational (lhs.n * hrs.n, lhs.d * rhs.d);

return *result;

}


여전히 생성자가 호출되어야한다.

그리고 new로 할당한 메모리를 누가 해제를 해주는가?

operator* 로부터 반환되는 참조자 뒤에 숨겨진

포인터에 대해서는 사용자가 어떻게 접근할 방법이 없다.

즉, 자원누출이 심각한 코드가 된다.




스택에 할당하든 힙에 할당하든 생성자를 한번은 호출한다.

그렇다면 정적 객체로 함수 안에 정의해 놓고 참조자를 반환해보자.

const Rational& operator*(const Rational% lhs, const Rational& rhs){

static Rational result;

result = ...;

return result;

}




operator==가 선언되었고 a,b,c,d 모두 Rational 객체

if((a*b) == (c*d)); 

위의 표현식은 항상 값이 true이다.


왜? operator*은 정적 객체의 참조자를 반환한다.

둘의 값이 같지 않을 수가 없다.


이쯤되면 정적 배열을 써보는 것은 어떨까?

함수가 불릴 때마다 다른 객체를 반환해야 하기 때문에

n번의 호출이 있다면 n개의 배열이 있어야 한다.

비록 한번이지만 생성자가 n번 호출되고 소멸자가 n번 호출된다.

그리고 객체에 값을 어떻게 넣을 것인가?




그냥 새로운 객체를 반환하도록 하자!

inline const Rational operator*(const Rational& lhs, const Rational& rhs){

Rational result (lhs.n * hrs.n, lhs.d * rhs.d );

return result;

}




이 코드에도 반환 값을 생성하고 소멸시키는 비용이 들지만

올바른 동작에 지불되는 작은 비용이다. 또한 몇몇 조건하에서는

최적화 메커니즘에 의해 operator*의 반환 값에 대한 생성과

소멸 동작이 안전하게 제거 될 수 있다. (반환 값 최적화 : RVO)


POINT!!

값에 의한 반환이 최선이라면 그냥 그렇게 하자.

그리디 알고리즘 06 공주님의 정원


처음으로 한방에 성공~





01 클래스 선언




02 초기화



03 정렬




04 재귀함수 호출 그리고 답 출력




05 메인 함수




이제 백트래킹으로 넘어가 봅시다~

값에 의한 전달 보다는 상수객체 참조자에 의한 전달 방식을

택하는 편이 대개 낫다.


기본적으로 C++는 함수로부터 객체를 전달받거나

함수에 객체를 전달할 때 값에 의한 전달 방식을 사용한다.

실제 인자의 사본을 통해 초기화 되며 그 함수가 반환한

값 또한 사본을 돌려받는다. 이는 복사생성자에 의해 수행되며

때에 따라서는 고비용 연산이 되기도 한다.


class AAA{             --> Base 클래스

private:

string name;

string address;

public:

AAA();

virtual ~AAA();

}


class BBB : public AAA {  --> Drived 클래스

private:

stirng bName;

stirng bAddress;

...

}


bool validate(BBB b); --> BBB값으로 전달 받는 함수


BBB bbb;

bool check = validate(bbb);




bbb로부터 매개변수 b를 초기화 시키기 위해

1. BBB는 Drieved 클래스이기 때문에 Base 

   먼저 생성 되어야 한다. AAA의 복사생성자 호출

2. AAA 클래스 안의 stiring 생성자 호출.

3. string이 두개이기 때문에 또 생성자 호출.

4. BBB의 복사생성자 호출

5. BBB객체의 string 생성자 호출

6. string이 두개이기 때문에 또 생성자 호출.


7. BBB의 소멸자 호출

8. string 소멸자 호출

9. stirng 소멸자 호출

10. AAA의 소멸자 호출

11. string 소멸자 호출

12. stirng 소멸자 호출




상당히 고비용이다. 따라서 우리는

상수객체에 대한 참조자로 전달하게 만들자.


bool validate(const BBB& b);


새로 만들어지는 객체는 없기 때문에

생성자와 소멸자가 전혀 호출되지 않는다.

값을 전달 받는 validate함수는 전달되는 BBB에 

어떠한 변화가 생겨도 그 변화로부터 안전하게

보호를 받는다. (사본이 넘겨지기 때문에)

이번에는 참조에 의한 전달 방식을 사용했기 때문에

원본을 그 변화로 부터 지켜줘야 한다. --> const




또한 참조에 의한 전달 방식은 복사손실 문제도 해결해준다. 

복사 손실이란?

Drived 클래스 객체가 Base 클래스 객체로서

전달되는 경우가 있다. (함수의 매개변수로 넘길 때)

이 때 객체가 값으로 전달되면 Base 클래스의

복사 생성자만이 호출되고, Drived 클래스의

특징은 잘려나가게 되는데 이를 복사 손실이라 한다.


class AAA{   --> Base 클래스

...

public:

string GetName() const;

virtual void show() const;

};


class BBB : public BBB{  --> Drived 클래스

...

public:

...

virtual void show() const;

};


void print(AAA a){  --> AAA값을 받는 함수

cout<<a.name;

a.show()

}


BBB b;

print(b);




위의 경우 값으로 전달되어 복사가 된다.

Drived클래스의 정보는 잘려나가게 된다.

따라서 print함수의 show()는 절대 BBB의 것이 될 수 없다.


void print(const AAA& a);

print(b);


상수 객체 참조자에 의한 전달 방식을 사용한다면

이러한 문제를 말끔하게 해결 할 수 있다.

참조자를 전달한다는 것은 결국 포인터를

전달한다는 것과 일맥상통하기 때문이다.




하지만 참조자에 의한 전달이 어떤 상황에서든

빛을 발하는 것은 아니다. 기본제공 타입의 경우에는

값으로 넘기는 편이 더 효율적일 때가 많다.

기본제공 타입 외에도 STL의 반복자, 함수객체도 마찬가지.

따라서 반복자와 함수 객체를 구현할 때는 반드시

1. 복사 효율을 높일 것과

2. 복사손실 문제에 노출되지 않도록

만드는 것이 필수이다.




기본제공 타입은 작다. 따라서 타입 크기만

작으면 모두 값에 의한 전달이 더 효율적인가?

--> 크기가 작아도 비용이 비쌀 수 있다.


만약 복사의 비용이 그렇게 비싸지 않더라 하더라도

컴파일러 중에는 기본제공 타입과 사용자 정의 타입을

아예 다르게 취급하는 것들이 있다. 기본제공 타입은

레지스터에 넣어주지만, 사용자 정의 타입 객체는

아무리 기본제공 타입과 비슷하더라도 넣어주지 않는다.

이러한 개발 환경이라면 참조에 의한 전달을 쓰는 것이 낫다.

포인터만큼은 확실하게 레지스터에 들어간다.




복사 비용이 싸더라도 값에의한 전달을 할 수 없는

또 하나의 이유는 사용자 정의 타입의 크기는 언제든

변화에 노출되어 있다는 것이다. 지금은 작더라도

유지보수를 거듭하게 되면 언제 커질지 모르는 것이다.


따라서 기본제공 타입, STL의 반복자, 함수객체 타입

위 세가지 말고는 그냥 상수객체 참조자에 의한 전달방식을 쓰자.




POINT!!

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

2. 기본제공 타입, STL의 반복자, 함수객체 타입은 값에의한 전달



그리디 알고리즘04 도서관 문제


이번 문제는 푸는 동안 자연스럽게 흘러 가는게 아니라

뭔가 끼워 맞추듯 부자연스러운 느낌이 강했다.

뭔가 더 좋은 방법이 있을거 같은데..




01. 클래스 선언




02. 초기화




03. 정렬




04. 답 찾기 그리고 답 출력




05. 메인 함수


대충 흐름은 알겠고..

조금만 더 빨리 푸는 연습을 해야 할 거같다.


그리디 알고리즘03 냉장고 문제 (정올)


문제를 이해하는 데 시간이 엄청 걸렸다.
많이 풀어보는 것 말고는 답이 없는 거같다.





01. 클래스 선언




02. 초기화




03. 정렬을 위한 함수 객체 그리고 정렬




05. 답을 찾는 재귀 함수와 답 출력 함수




06. 메인 함수


그리디는 감이 좀 오는 듯하다. 앞으로 한 두 문제만 더 풀어보겠다.



오늘은 C++의 캐스트 연산자에 대해 알아보겠다.


01. static_cast


c언어의 형변환과 유사하다.


double d = 5.5;

int n = static_cast<int>(d);


C에서는 n = (int)d;

실수형에서 정수형으로 형변환을 하였다.

당연히 소수점 부분은 잘려버리게 된다.

하지만 컴파일러가 허용하는 범위이기 때문에 가능.




stirng str = "abcd";

int n = static_cast<int>(str);

string을 int로 바꾸고 있다. 

이 경우는 컴퍼일러가 허용하지 않는다.


그리고 static_cast는 포인터 형변환이 안된다.


int n = 10;

int *pi;

double *pd;


pi = &n;

pd = static_cast<double*>(&n);

이런 식으로의 포인터 형변환은 되지 않는다.




포인터 형변환이 의미가 있는 경우는

클래스 상속과 연관될 때이다.


class AAA{

public:

int na;

};

calss BBB : public AAA {

public:

int nb;

};


AAA a;

AAA *pa;

BBB b;

BBB *pb;


pa = static_cast<AAA*>(&b);       1번

pb = static_cast<BBB*>(&a);      2번


2번의 경우 Base 클래스 자료형의 값을

Drieved 클래스 자료형에 할당하고 있기 때문에

다운 캐스팅을 하는 경우이다. 1번은 2번과

반대의 경우인 업 캐스팅을 하는 경우이다.


업캐스팅의 경우에는 문제가 안되지만

다운캐스팅의 경우에는 문제가 생긴다.




1번의 경우 pa가 Drived 객체에 있는 멤버에

접근을 하지 못하지만 문제가 되진 않는다.

하지만 2번의 경우 즉 다운캐스팅이 된 경우를 보자.

Drived 클래스에는 Base에는 없는데 자신만 가지고 있는

멤버의 존재가 가능하다. 이 경우 원래 존재하지도 않는

값을 참조하게 될 수 있다는 문제가 발생한다.

pb -> nb; 같은 식으로 말이다.




02. dynamic_cast


위와 같은 문제 때문에 나타난 것이 바로 dynamic_cast이다.

상속 관계에 있는 클래스 포인터끼리의 형변환을 허용하되,

다운 캐스팅이 일어나면 null 값을 대신 리턴하여 위험을 알린다.


따라서 이 형변환을 사용할 때는 먼저 결과가 null이

아닌지 확인하는 것이 좋다. 또한 이 형변환을 사용하기 위해서는

클래스가 다형성을 가져야 한다. 가상함수가 있어야 한다는 말.


class AAA{

public:

virtual void print() { cout<<"부모 객체"<<endl }

};


class BBB : public AAA{

public:

virtual void print() { cout<<"자식 객체"<<endl }

};




AAA aaa;

AAA *pa;

BBB bbb;

BBB *pb;


1번

pa = dtnamic_cast<AAA*>(&aaa);

if(pa) pa->printf();

else cout<<"캐스팅 실패"<<endl;


2번

pa = dtnamic_cast<AAA*>(&bbb);

if(pa) pa->printf();

else cout<<"캐스팅 실패"<<endl;


3번

pb = dtnamic_cast<AAA*>(&aaa);

if(pa) pa->printf();

else cout<<"캐스팅 실패"<<endl;


4번

pb = dtnamic_cast<AAA*>(pa);

if(pa) pa->printf();

else cout<<"캐스팅 실패"<<endl;




1번의 경우 당연히 성공

2번의 경우도 당연히 성공 (다형성의 중요한 부분)

3번의 경우 다운캐스팅이기 때문에 실패한다.

4번의 경우 Base 형 포인터의 값을 가져왔으나

그게 사실 Drived 형 객체를 가리키기 때문에 성공.


따라서 위의 결과는

부모 객체

자식 객체

캐스팅 실패

자식 객체




위에서 살펴본 형변환의 이점은 가독성과 디버깅의 편리함이다.

dynamic_cast는 엉뚱한 다운캐스팅으로 인한 오류를

경우에 따라 훨씬 빨리 찾아내는게 가능해진다.



인터페이스 설계는 제대로 쓰기엔 쉽게,

엉터리로 쓰기엔 어렵게 하자.


날짜를 나타내는 어떤 클래스가 있다.

class Date{

public:

Date( int month, int day, int year );

...


};


Date d( 1990 , 7 ,13 );

Date d( 20, 12, 2003);

매개변수의 전달 순서가 잘 못 될 수 있다.




해결책

새로운 타입을 들여와 인터페이스를 강화하자.

일,월,연을 구분하는 간단한 랩퍼 타입을 만들고

이 타입을 Date 생성자 안에 둔다.


struct Day{

int val;

explicit Day (int d) : val (d) {};

};

struct Month{

int val;

explicit Day (int m) : val (d) {};

};

struct Year{

int val;

explicit Day (int y) : val (d) {};

};


class Date {

public:

Date( const Month& m , const Day& d , const Year& y );

...

};


Date d (Month(3) , Day(3) , Yeay(1995));




적절한 타입만 제대로 준비되어 있다면, 각 타입에

값에 제약을 가하더라도 괜찮은 경우가 생긴다.

위의 경우, 월이 가질 수 있는 값은 12개 뿐이므로

enum을 사용해서 값에 제약을 줄 수 있다.

하지만 enum의 타입 안전성은 그리 믿음직하지

못하기 때문에, 유효한 Month의 집합을 미리 정의해 두자.


class Month {

private:

explicit Month(int m); --> Month 값이 새로 생성 되지 않도록 하기위해 private로 선언

--> 이미 밑의 함수에서 Month가 리턴되고 있기 때문에 생성될 필요없음.

public:

static Month Jan() {return Month(1);}  --> 왜 함수를 쓰느냐?

...                                                            --> 비지역 정적 객체들의 초기화 문제

static Month Dec() {return Month(12);}

...

};


Date d(Month::Mar(), Day(30), Year(1995));



예상되는 사용자 실수를 막는 다른 방법으로는

어떤 타입이 제약을 부여하여 그 타입을 통해

할 수 있는 일들을 묶어 버리는 방법이 있다.

제약 부여 방법으로 아주 흔히 쓰이는 예가 const 이다. 

앞 전 포스팅에서 다뤘기 때문에 패스~




사용자 정의 타입은 기본제공 타입처럼 동작하게 만들어야 한다.

기본제공 타입과 쓸데없이 어긋나는 동작을 피하는 실질적인

이유는 일관성 있는 인터페이스를 저공하게 위함이다.

STL의 모든 컨테이너는 size란 멤버 함수를 개방해 놓고 있다.

이 함수는 어떤 컨테이너에 들어 있는 원소의 개수를 알려준다.

만약 이러한 함수가 컨테이너 마다 다르다면? 혼란의 여지가 있다.


사용자 쪽에서 뭔가를 외워야 제대로 쓸 수 있는 인터페이스는 잘못 쓰기 쉽다.




Investment* createInvestment(); -> 팩토리 함수

요 앞전에 다뤘던 팩토리 함수에서 반환되는 포인터를

auto_ptr 과 shared_ptr로 관리를 해주는 얘기를 했었다.

여기에서도 인터페이스에 관한 얘기를 할 수 있는데,

스마트 포인터를 사용해야 한다는 사실을 잊을 경우 이다.

--> 애초부터 팩토리 함수가 스마트 포인터를 반환하게 만들자.


shared_ptr<Investment> createInvestment();


shared_ptr을 반환하는 구조는 자원 해제에 관련된

상당수의 사용자 실수를 사전 봉쇄할 수도 있어서

여러모로 인터페이스 설계자에게 좋다.

생성 시점에 삭제자를 직접 엮을 수 있는 기능이 있기 때문.




createInvestment를 통해 얻은 investment* 포인터를

직접 삭제하지 않게 하고 getRidOfInvestment라는

함수를 준비하고 여기에서 삭제를 진행한다고 가정해보자.

--> getRidOfInvestment 대신 delete를 쓸 수 있는 가능성이 있다.


shared_ptr에는 두 개의 인자를 받는 생성자가 있다.

첫 번째 인자는 이 놈이 관리할 실제 포인터이고,

두 번째 인자는 참조 카운트가 0이 될 때 호출될 삭제자이다.

shared_ptr이 널 포인터를 물게 함과 동시에 삭제자로

getRidOfInvestment 를 갖게 하는 방법을 쓸 수 있다.




shared_ptr<Investment> pInv(0,getRidOfInvestment);

--> 컴파일 에러가 난다. 생성자의 첫 번째 매개변수로

포인터를 받아야 하는데 0은 포인터가 아니라 int이다.


캐스트를 적용하여 문제를 해결한다.

shared_ptr<Investment>

pInv(static_cast<Investment*>(0),getRidOfInvestment);




이제 위에 놈을 반환하도록 구현해보자.

shared_ptr<investment> createInvestment(){

shared_ptr<investment> retVal (static_cast<Investment*>(0),

,getRidOfInvestment);

...

retVal = ... ;      --> 실제 객체를 가리키도록 만든다.


return retVal;

}


--> 실제 객체의 포인터를 결정하는 시점이

retVal을 생성하는 시점보다 앞설 수 있으면,

위의 코드 처럼 retVal을 널로 초기화하고 대입하는 것보다

실제 객체의 포인터를 바로 생성자에 넘겨버리는 것이 낫다.




shared_ptr은 기본적으로 교차 DLL문제를 미연에 방지해준다.

교차 DLL문제란 객체 생성 시에 어떤 동적 링크 라이브러리의

new를 썻는데 그 객체를 삭제할 때는 이전의 DLL과

다른 DLL의 delete를 썼을 경우이다.


하지만 shared_ptr은 포인터마다 각각의 

삭제자를 자동으로 쓰기 때문에 자신이 가리키는

DLL을 잊지 않고 있다가 해당하는 DLL의 delete를 사용한다.




결론

사용자가 무심코 저지를 수 있는 실수를 없앰으로써

좋은 인터페이스를 만다는 데 쉽게 다가갈 수 있다.


POINT!!

1. 인터페이스 사이의 일관성 잡아주기, 기본제공 타입과 똑같이 동작하게 하기.

2. 사용자의 실수를 방지하는 방법으로 새로운 타입 만들기, 타입에 대한 연산을 제한하기,

객체의 값에 대해 제약 걸기(const), 자원 관리 작업을 사용자 책임으로 놓지 않기(팩토리 함수의 반환값을 스마트포인터로)

3.  shared_ptr은 사용자 정의 삭제자를 지원한다. 때문에 교차 DLL 문제를 막아준다.



new로 생성한 객체를 스마트 포인터에 저장하는

코드는 별도의 한 문장으로 만들자.


처리 우선순위를 알려 주는 함수가 있고,

동적으로 할당한 Data 객체에 대해 어떤

우선 순위에 따라 처리를 적용하는 함수가 있다.


int p();

void processData ( shared_ptr<Data> pd, int p );


1. processData (new Data, p);

--> 컴파일 오류가 난다.

shared_ptr의 생성자는 explicit으로

선언 되어 있기 때문에, 'new Data' 라는

표현식에 의해 만들어진 포인터가

shared_ptr 타입의 객체로 바꾸는

암시적인 변환이 가능할 수 없다.


2. pricessData( shared_ptr<Data>(new Data) , p());

--> 정상적으로 동작한다.




컴파일러는 processData 호출 코드를 만들기 전에

이 함수의 매개변수로 넘겨지는 인자를 평가 한다.

첫 번째 인자 shared_ptr<Data>(new Data)는

1. new Data 표현식을 실행하는 부분과

2. shared_ptr 생성자를 호출하는 부분으로 나눠진다.




따라서 processData 함수 호출이 이루어지기 전에

컴파일러는 다음의 세 가지 연산을 위한 코드를 만든다.


1. p를 호출.

2. new Data를 실행.

3. shared_ptr 생성자 호출.


하지만 각각의 연상이 실행되는 순서는

컴파일러마다 다르다는게 문제점이다.

따라서 new Data 부분이 shared_ptr보다 먼저

호출되어야 하지만 이 부분을 장담할 수 없다.




1. new Data를 실행.

2. p를 호출.

3. shared_ptr 생성자 호출.


위와 같이 실행이 되도 문제가 생길 수 있다.

p를 호출하는 부분에 예외가 발생했다면

new Data로 만들어졌던 포인터가 유실될 수 있다.




해결책

Data를 생성해서 스마트 포인터에

저장하는 코드를 별도의 문장 하나로 만들고,

그 스마트 포인터를 processData에 넘기는 것이다.


shared_ptr<Dara> pd(new Data);

--> new로 생성한 객체를 스마트 포인터에

담는 코드를 하나의 독립정인 문장으로 만든다.

processData(pd, p());

--> 인자의 평가 순서가 바뀌어도

자원 누출의 가능성이 없다.




POINT!!

new로 생성한 객체를 스마트 포인터로 넣는 코드는

별도의 한 문장으로 만들자. 이것이 안 되어 있으면,

예외가 발생될 때 디버깅하기 힘든 자원 누출이 초래될 수 있다.



+ Recent posts