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

이를 통해 클래스 제작자는 문법적으로 일관성있는 데이터 접근 통로를 제공 할 수 있고

필요에 따라서 세밀한 접근 제어도 가능하며 클래스의 불변속성을 강화 할 수 있을 뿐 아니라

내부 구현의 융통성도 발휘 할 수 있다.

protected는 public보다 더 많이 보호 받고 있는 것이 절대로 아니다.

const Rational& operator*( const Rational& lsh, const Rational& ths )

{

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

return result;

};


복사생성자가 한번더 불리는게 싫어서 이렇게 한다면

지역객체의 경우엔 return과 동시에 소멸된다.


result안에 기본자료형만 있으면 다행인데 여러 객체가 있다면 버그를 만든다.


객체를 반환하여 생기는 복사생성 비용은 안전하게 하는데 드는 비용일 뿐이고

실제로 그리 비용이 많이 들지 않는다.

기본적으로 C++은 함수로부터 객체를 전달받거나 함수에 객체를 전달 할 때 "값에 의한 전달 "

pass-by-value 방식을 사용 한다.( C에서 물려받은 특성중 하나 ) 특별히 다른 방식을 지정하지

않는 한, 함수 매개변수는 실제 인자의 사본을 통해 초기화 되며, 어떤 함수를 호출 한 쪽은

그 함수가 반환하는 값의 사본을 돌려받는다. 이들 사본을 만들어내는 원천이 바로 복사생성자인데

이 점 떄문에 값에 의한 전달이 고비용의 연산이 되기도 한다.

아래 클래스를 보면

class Person

{

public:

Person();

virtual ~Person();

private:

std::string name;

std::string address;

};


class Student : public Person

{

public:

Student();

~Student();

private:

std::string schoolName;

std::string schoolAddress;

};


여기서 아래를 보면 validateStudent라는 함수를 호출하고 있다.

이 함수를 Student 인자를 전달받고(값으로) 이 인자가 유효화 됬는가를 알려주는 값을 반환한다.

bool validateStudent( Student S );        // Student를 값으로 전달받는 함수

Student plato;                         

bool platoIsOk = validateStudent(plato);    // 함수 호출

plato로부터 매개변수 s를 초기화 시키기 위해 Student의 복사 생성자를 호출한다.

게다가 s는 validateStudent가 복귀할 때 소멸될 것이고, 이 함수의 매개변수 전달 비용은

Student의 복사 생성자 호출 한번, Student의 소멸자 호출 한번 이다.


Student객체에는 String 객체 두개가 멤버로 들어 있기 떄문에..Student객체가 생성될 때마다

이들 String 형제도 덩달아 생성되야 한다. 게다가 Student객체는 Person객체로부터

파생되었기 때문에 Student객체가 생성되면 Person객체도 먼저 생성되야 한다.

Person객체 안에는 또 String 객체 두개가 들어 있ㄱ ㅣ때문에 Person 객체가 생성 될 떄마다

String 생성자가 두번 더 불리게 된다.

단지 Student 객체 하나를 값으로 전달 했을 뿐인데 Student 복사 생성자 호출한번,

Person 복사 생성자 호출 한번에 추가로 String 복사 생성자 호출이 네번 일어 난다.

Student 객체의 사본이 소멸될때도 앞에 생성된 생성자들 각각이 소멸자 호출과 대응된다.

Student 객체응 값으로 전달하는데 날아간 비용만 보면 생성자 여섯번에 소멸자 여섯번이다.


이렇게 여러번 거치지 않고 넘어 갈 수 있는 방법이 있다. 그게 바로 상수객체에 대한 참조자

( Reperance - to - const )로 전달하게 만드는 것이다.

bool validateStudent( const Student& s );

이렇게 하면 새로 만들어지는 객체 같은 것이 없기 때문에 생성자와 소멸자를 전혀 호출되지 않는다.

여기서 새겨둬야 할 부분이 매개변수 선언문에 있는 const이다.

원래의 validateStudent는 Student매개변수를 값으로 받도록 되어 있기 때문에, 호출부에서는 함수로

전달된 Student객체에 어떤 변화가 생기더라도 그 변화로부터 안전하게 보호를 받는다는 점을 알고있다. 그도 그럴 것이 validateStudent가 상대하는 Student 객체는 원본이 아닌 사본이니까.

그런데 이제 Student 객체의 전달 방식이 참조에 의한 전달이다.

참조에 의한 전달방식으로 매개변수를 넘기면 복사손실 문제( slicing problem )가 없어지는 장점도

있다. 


※ 값에 의한 전달 보다는 상수 객체 참조자에 의한 전달을 선호하자. 대체적으로 효율 뿐만아니라

복사손실 문제까지 막아준다.

※ 기본제공 타입 및 STL반복자, 그리고 함수 객체 타입에는 맞지 않다. 이들에 대해서는

값에 의한 전달이 더 적절하다

C++에서 새로운 클래스를 정의 한다는 것은 새로운 타입을 하나 정의하는 것과 같다.

클래스를 설계할 때는 마치 언어 설계자가 그 언어의 기본제공 타입을 설계하면서 쏟아붓는 것과

똑같은 정성과 보살핌이 필요하다.


1. 새로 정의한 타입의 객체 생성 및 소멸은 어떻게 이루어져야 하는가?

이 부분이 어떻게 되느냐에 따라 클래스 생성자 및 소멸자의 설계가 바뀐다.

그리고 메모리 할당 함수( operator new, operator new[], operator delete, operator delete [] )를

직접 작성 할 경우 이들 함수의 설계에도 영향을 미친다.


2. 객체 초기화는 객체 대입과 어떻게 달라야 하는가?

생성자와 대입 연산자의 동작 및 둘 사이의 차이점을 결정짓는 요소다. 초기화 대입을 헷갈리지 않는

것이 가장 중요한데, 각각에 해당되는 함수 호출이 아에 다르기 때문이다.


3. 새로운 타입으로 만든 객체가 값에 의해 전달되는 경우에 어떤 의미를 줄것인가?

여기서 잊으면 안되는 포인트 하나를 알려준다. 어떤 타입에 대해 "값에 의한 전달"을 구현하는 쪽은

복사 생성자 이다.


4. 새로운 타입이 가질 수 있는 적법한 값에 대한 제약은 무엇으로 잡을 것인가?

전부는 아니지만, 클래스의 데이터 멤버의 몇 가지 조합 값만은 반드시 유효해야 한다. 이런 조합을

가리켜 클래스의 불변속성( invariant ) 이라고 하며, 클래스 차원에서 지켜주어야 하는 부분이다.

이 불변속성에 따라 클래스 멤버 함수 안에서 해 주어야 할 에러 점검 루틴이 좌우되는데,

특히 생성자, 대입 연산자, 작종 " 쓰기(setter) "함수는 불변속성에 많이 좌우된다. 그뿐 아니라

불변속성은 여러분의 함수가 발생시키는 예외에도 영향을 미치며, 혹시나 여러분이 예외지정

(exceprion specification)을 쓴다면 그 부분에도 영향을 준다.


5. 기존의 클래스 상속 계통망( inheritance graph ) 에 맞출 것인가?

이미 갖고있는 클래스로부터 상속 시킨다면, 당연히 여러분의 설계는 이들 클래스에 의해

제약을 받게 된다. 특히 멤버 함수가 가상인가 비가상인가의 여부가 가장 큰 요인이다. 여러분이

만든 클래스의 다른 클래스들이 상속 할 수 있게 만들자고 결정했다면, 이에 따라 멤버 함수의 가상

함수 여부가 결정된다. 특히 소멸자가 그렇다.


6. 어떤 종류의 타입 변환을 허용 할 것인가?

허용 하고 싶은 타입 변환에 따라 명시적(explcit), 암시적(implicitly)으로 함수를

만들지 말지 결정.


7. 어떤 연산자와 함수를 두어야 의미가 있을까?

클래스 안에 선언 할 함수가 바로 여기 결정된다. 어떤것은 멤버 함수로 적당할 것이고,

또 몇몇은 그렇지 않을 것이다.


8. 표준 함수들 중 어떤 것을 허용하지 말 것인가?

private로 선언해야 하는 함수가 여기에 해당된다.


9. 새로운 타입의 멤버에 대한 접근권한을 어느 쪽에 줄것인가?

어떤 멤버를 public, private, protected 영역에 둘 것인가를 결정을 말함


10. "선언 되지 않은 인터페이스"로 무엇을 둘 것인가?

타입이 제공할 보장이 어떤 종류일까. 보장 할 수 있는 부분은 수행 성능 및 예외 안전성

그리고 자원 사용 등등이다.


11. 새로 만드는 타입이 얼마나 일반적인가?

실상은 타입 하나를 정의하는 것이 아닐지도 모른다. 정의하는 것이 동일 계열의 타입군(family of

types)전체일지도 모른다.진짜 그렇다면 원하는 것은 새로운 클래스가 아니다. 새로운 클래스

템플릿을 정의해야 할것이다.


12. 정말로 꼭 필요한 타입인가?

기존의 클래스에 대해 기능 몇 개가 아쉬워서 파생 클래스를 새로 뽑고 있다면, 차라리 간단하게

비멤버 함수라던지 템플릿을 몇 개 더 정의하는 편이 낫다.


※ 클래스 설계는 타입 설계다. 새로운 타입을 정의하기 전에 이번 항목에 나온 모든 고려사항을

빠짐없이 점검해 봐야 한다.

흔하게 할 수 있는 실수들 중에서

매개변수 순서를 잘 못 입력한 경우가 생길 수 있다.


class Data

{

public:

Date( int Month, int Day, int Year );

}

Date( 3, 10, 1999 ) ; 라도 제대로 기입 한다면 문제가 없겠지만

입력중 오타 라던지, 여러 사람이 사용 할 경우 순서가 헤깔리는 경우가 생 길 수 있다.

이럴때 명시적으로 각자를 구분하는 간단한 랩퍼(wrapper)타입을 만들고 이 타입을

Date 생성자 안에 둘 수 있다.

struct Day{                                struct Month{                        struct Year{

explcit Day( int d )                    explcit Month( int m )            explcit Year( int y )

: val( d ) {}                              : val( m ) {}                         : val( y ) {}

int val;                                     int val;                                 int val;

};                                             };                                        };

class Date{

public:

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

};

Date d(30, 3, 1995);                                      // error

Date d( Day(30), Month(3), Year(1995) );        // error

Date d( Month(3), Day(30), Year(1995) );        // Ok

이렇게 명시적으로 사용 하면 실수를 줄일 수 있다.

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

객체의 값에 대한 제약걸기, 자원 관리 작업을 사용자 책음으로 놓지 않기가 있다.

new로 생성한 객체는 delete로 해제 시키고

new []로 생성한 객체는 delete []로 해제 시키자는 그런 내용.

단일 객체와 배열 객체의 구분을 잘 해야한다.

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

Posted 2012. 10. 18. 17:59

객체삭제를 엄청 신경써서 하라는 내용.

혹시 객체를 삭제 하지 않았을때를 위해 auto_ptr과 shared_ptr을 얘기해고 있다.


자원 누출을 막기위해 생성자 안에서 자원을 획득하고 소멸자에서 그것을 해제하는

RAII객체를 사용하고

일반적으로 널리 쓰이는 RAII 클래스는 tr1::shared_ptr과 auto_ptr이다

이 둘 가운데 tr1::shared_ptr이 복사 시의 동작이 직관적이기 때문에 더 좋다.

반면 auto_ptr은 복사되는 객체를 NULL로 만들어 버린다.


즉.. 스마트 포인터에 대한 설명

부분복사가 일어 날수있으니 주의해야 한다.

객체 복사 함수는 주어진 객체의 모든 데이터 멤버 및 모든 기본 클래스 부분을 빠뜨리지 말고 

복사 해야 한다.

클래스 복사 함수 두개를 구현 할 때, 한쪽을 이용해서 다른 쪽을 구현하려는 시도는 하지말라.

그 대신, 공통된 동작을 제3의 함수에다 분리해 놓고 양쪽에서 이것을 호출하게 만들어서 해결하자.

생성자 혹은 소멸자 안에서 가상 함수를 호출 하게 되면

지금 실행중인 생성자나 소멸자에 해당되는 클래스의 파생 클래스 쪽으로 내려가지 않는다.


음. 지금 기본 클래스를 초기화 중인데 파생 클래스의 것을 호출하러 가지 않는 다는거.ㅇㅇ

이건뭐 기본 클래스에 소멸자에서 동적해제 할때를 말하는건데

virtual을 붙이지 않으면 파생클래스 객체를 기본 클래스 포인터로 삭제할때

파생클래스 소멸자가 호출 되지 않는다는거..


그러니 다형성할 기본 클래스라면 소멸자에 virtual을 붙여라 능거


« PREV : 1 : 2 : 3 : NEXT »