Accelerated C++에서 발췌(p.369~396)
타입 의존성이 존재하는 코드
다음 코드에서 사용된 Core는
여기를 참조.
vector students; //다형적 타입이 아닌 Core 객체를 담는다.
Core record; //Core 객체. Core로부터 파생된 타입이 아님.
record의 타입은 Core이고, students는 Core타입을 담는 vector라고 명시했으므로 타입 의존성이 매우 명백하다.
위 코드를 보면서 vector
라고 정의했을 때, vector의 각 객체가 Core 객체이지, Core로부터 파생된 타입의 객체가 아니라는 것을 아는 것이 중요하다.
타입 의존성을 제거하기 위해서...
다음과 같이 포인터로 변경한다.
vector students; //객체가 아닌 포인터를 저장한다.
Core* record; //임시변수도 포인터여야 한다.
또한 기본 타입의 포인터를 통해 파생 타입의 객체를 소멸시킬 수 있어야 하는 경우라면, virtual 소멸자를 사용해야 한다.
포인터를 저장하는 방식의 문제점
1. 포인터를 직접 관리해야 하는 추가적인 부담이 있다.
2. 그로 인해 버그를 발생시킬만한 여지가 많다.
사용자들은 레코드를 읽을 때 공간을 할당하는 것을 잊지 말아야 하며, 더 이상 데이터가 필요없게 되었을 때 그 공간을 해제하는 것도 잊지 말아야 한다.
3. 내부 객체들에 접근하려면 항상 포인터를 역참조해야 하는 불편이 있다.
핸들 클래스
Grad는 Core로부터 파생된 클래스이다. Core에 대한 포인터를 사용하면, 그 포인터는 Core객체를 가리킬 수도 있고 Grad객체를 가리킬 수도 있기 때문에 포인터를 사용하는 방법을 선택하였다.
하지만 이 방법은 사용자들에게 오류의 위험이 다분한 여러 가지 요구사항을 부여한다는 것이 문제이다.
이러한 요구사항을 없애버릴 수는 없지만, Core에 대한 포인터를 캡슐화시킨, 새로운 클래스를 사용하면 사용자에게 안보이게 할 수는 있다.
class Student_info
{
public:
//생성자들 및 복사 제어(copy control)
Student_info() : cp(0) {}
Student_info(std::istream& is) : cp(0) { read(is); }
Student_info(const Student_info&);
Student_info& operator=(const Student_info&);
~Student_info() { delete cp; }
//연산들
std::istream& read(std::istream&)
{
delete cp;//만약 있다면 이전 객체를 해제함.
char ch;
is >> ch; //레코드 타입을 얻음.
if (ch == 'U')
{
cp = new Core(is);
}
else
{
cp = new Grad(is);
}
return is;
}
std::string name() const
{
if (cp) return cp->name();
else throw std::runtime_error("uninitialized Student");
}
double grade() const
{
if (cp) return cp->grade();
else throw std::runtime_error("uninitialized Student");
}
private:
Core* cp;
};
여기에서의 아이디어는 Student_info 객체로 Core나 Grad를 나타내도록 하는 것이다.
즉, 포인터처럼 동작하는 것이다. 하지만, 사용자들은 Student_info에 바인딩되는 내부 책체의 할당에 대해서는 신경쓸 필요가 없다.
지루하고 에러 위험이 있는 작업들은 클래스가 알아서 처리한다.
참조:p.374
핸들 객체 복사
Core 클래스와 Grad 클래스에 다음 코드를 추가.
class Core
{
friend class Student_info;
protected:
virtual Core* clone() const { return new Core(*this); }
//나머지는 이전과 동일
};
class Grad
{
protected:
Grad* clone() const { return new Grad(*this); }
//나머지는 이전과 동일
};
Student_info 클래스에 다음 코드를 추가
Student_info(const Student_info& s)
{
if (s.cp) cp = s.cp->clone();
}
Student_info& operator=(const Student_info& s)
{
if (&s != this)
{
delete cp;
if (s.cp)
cp = s.cp->clone();
else
cp = 0;
}
return *this;
}
핸들 클래스 사용하기
int main()
{
vector<Student_info> students;
Student_info record;
string::size_type maxlen = 0;
//데이터를 읽고 저장한다.
while (record.read(cin))
{
maxlen = max(maxlen, record.name().size());
students.push_back(record);
}
//이름과 성적을 출력한다.
for (vector<Student_info>::size_type i = 0; i != students.size(); ++i)
{
cout << students[i].name() << string(maxlen+1-students[i].name.size(), ' ');
try
{
double final_grade = students[i].grade();
streamsize prec = cout.precision();
cout << setprecision(3) << final_grade << setprecision(prec) << endl;
}
catch (domain_error e) {
cout << e.what() << endl;
}
}
return 0;
}
핸들 클래스의 템플릿 클래스화
template <class T>
class Handle
{
public:
Handle() : p(0) {}
Handle(const Handle& s) : p(0) { if (s.p) p = s.p->clone(); }
Handle& operator=(const Handle& rhs)
{
if (&rhs != this)
{
delete p;
p = rhs.p ? rhs.p->clone() : 0;
}
return *this;
}
~Handle() { delete p; }
Handle(T* t) : p(t) {}
operator bool() const {return p;}
T& operator*() const
{
if (p) return *p;
throw runtime_error("unbound Handle");
}
T* operator->() const
{
if (p) return p;
throw runtime_error("unbound Handle");
}
};
이 블로그를 구독하시려면 이 버튼을 눌러주세요 ===>
