| AcceleratedC++/Chapter13 | AcceleratedC++/Chapter15 |
| * 포인터를 복사하는 것은 그 대상 객체를 복사하는 것과는 다름. * 포인터의 소멸이 그 객체의 소멸을 의미하지 않는다. (memory leak) * 포인터 소멸시키지 않고, 객체를 소멸할경우 dangling pointer 발생. * 포인터 생성시 초기화하지 않으면, 포인터를 바인딩되지 않은 상태가된다. |
| * Handle은 객체의 참조값 * Handle은 복사가 가능하다 * Handle 객체가 다른 객체에 바인딩되어 있는지 확인이 가능 * Handle클래스가 가리키는 객체가 상속구조의 클래스형을 가리킨다면 virtual 에 의해 지정된 연산에대해서 다형성을 제공한다. |
~cpp
template <class T> class Handle {
public:
Handle(): p(0) { } // 기본생성자는 내부 멤버를 0으로 초기화하여서 아직 바인딩이 안된 상태임을 나타낸다.
Handle(const Handle& s) : p(0) { if (s.p) p = s.p->clone(); } // 복사 생성자는 인자로 받은 객체의 clone() 함수를 통해서 새로운 객체를 생성하고 그 것을 대입한다.
Handle& operator=(const Handle&);
~Handle() { delete p; }
Handle(T* t):p(t) { }
operator bool() const { return p; }
T& operator*() const;
T* operator->() const;
private:
T* p;
};
template<class T> Handle<T>& Handle<T>::operator=(const Handle& rhs) {
if(&rhs != this) { // 자기 참조를 조사한뒤 행동방식을 결정한다.
delete p;
p = rhs.p ? rhs.p->clone() : 0;
}
return *this;
}
~cpp Handle<Core> p(new Grad); // Handle<Core> == Core* // Handle<Core> 는 새로 생성된 Grad를 가리킨다.
~cpp
template<class T> T& Handle<T>::operator*() const {
if(p)
return *p;
throw runtime_error("unbound Handle");
} // 최기화를 할때 생성한 객체를 가리키는 결과를 리턴한다.
template<class T> T* Handle<T>::operator->() const {
if(p)
return p;
throw runtime_error("unbound Handle");
}
-> 연산자는 일견 이항 연산자 처럼 보이지만 동작하는 방식이 다른 연산자들과는 다르다. ->를 호출하게 되면 연산자의 좌측요소에서 포인터를 대신해서 사용이 가능한 요소가 리턴된다.~cpp // 동일한 표현 x->y; (x.operator->())->y; //as upper student->y; (student.operator->())->y; student.p->y;상기의 정의에서 *, -> 연산자를 참조형, 포인터형으로 리턴하게 함으로써 자동으로 파생객체에 대한 동적바인딩이 가능해 진다.
~cpp
//main.cpp
#include <algorithm>
#include <ios>
#include <iomanip>
#include <iostream>
#include <stdexcept>
#include <vector>
#include "Handle.h"
#include "Student_info.h"
using std::cin;
using std::cout;
using std::domain_error;
using std::endl;
using std::sort;
using std::streamsize;
using std::setprecision;
using std::setw;
using std::string;
using std::vector;
using std::max;
bool compare_Core_handles(const Handle<Core>& lhs, const Handle<Core>& rhs) {
return compare(*lhs, *rhs);
}
int main()
{
vector< Handle<Core> > students; // changed type
Handle<Core> record; // changed type
char ch;
string::size_type maxlen = 0;
// read and store the data
while (cin >> ch) {
if (ch == 'U')
record = new Core; // allocate a `Core' object
else
record = new Grad; // allocate a `Grad' object
record->read(cin); // `Handle<T>::->', then `virtual' call to `read'
maxlen = max(maxlen, record->name().size()); // `Handle<T>::->'
students.push_back(record);
}
// `compare' must be rewritten to work on `const Handle<Core>&'
sort(students.begin(), students.end(), compare_Core_handles);
// write the names and grades
for (vector< Handle<Core> >::size_type i = 0;
i != students.size(); ++i) {
// `students[i]' is a `Handle', which we dereference to call the functions
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;
}
// no `delete' statement
}
return 0;
}
Handle 클래스는 연결된 객체의 clone() 멤버함수를 이용한다. 따라서 Core클래스에 clone()메소드를 public으로 작성하는 것이 필요하다.~cpp
//Student_info.h
#ifndef GUARD_Student_info
#define GUARD_Student_info
#include <iostream>
#include <string>
#include "Core.h"
#include "Handle.h"
class Student_info {
public:
Student_info() { }
Student_info(std::istream& is) { read(is); }
// no copy, assign, or destructor: they're no longer needed
std::istream& read(std::istream&);
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");
}
static bool compare(const Student_info& s1,
const Student_info& s2) {
return s1.name() < s2.name();
}
private:
Handle<Core> cp; // Handle 클래스가 생성과 소멸을 자동화하기 때문에 복사 생성자, 대입 연산자, 소멸자가 필요 없다.
};
#endif
~cpp
//Student_info.cpp
#include <iostream>
#include "Student_info.h"
#include "Core.h"
using std::istream;
istream& Student_info::read(istream& is)
{
char ch;
is >> ch; // get record type
// allocate new object of the appropriate type
// use `Handle<T>(T*)' to build a `Handle<Core>' from the pointer to that object
// call `Handle<T>::operator=' to assign the `Handle<Core>' to the left-hand side
if (ch == 'U')
cp = new Core(is);
else
cp = new Grad(is);
return is;
}
자동으로 객체의 메모리 관리가 되기 때문에 delete 구문을 제거함. Handle::operator=()에 내부 메모리의 해제 구문이 들어있기 때문에 불필요.~cpp
//Ref_handle.h
#ifndef Ref_handle_h
#define Ref_handle_h
#include <cstddef>
#include <stdexcept>
template <class T> class Ref_handle {
public:
// manage reference count as well as pointer
Ref_handle(): p(0), refptr(new size_t(1)) { }
Ref_handle(T* t): p(t), refptr(new size_t(1)) { }
Ref_handle(const Ref_handle& h): p(h.p), refptr(h.refptr) {
++*refptr;
} // 참조형으로 동작하는 Handle인경우. 카운터의 주소를 대상객체의 카운터의 주소로 초기화, 카운터를 증가시킨다.
Ref_handle& operator=(const Ref_handle&);
~Ref_handle();
// as before
operator bool() const { return p; }
T& operator*() const {
if (p)
return *p;
throw std::runtime_error("unbound Ref_handle");
}
T* operator->() const {
if (p)
return p;
throw std::runtime_error("unbound Ref_handle");
}
private:
T* p;
std::size_t* refptr; // added
};
template <class T>
Ref_handle<T>& Ref_handle<T>::operator=(const Ref_handle& rhs)
{ // Ref_handle&를 인자로갖는 생성자와 마찬가지로 operator= 도 인자로 받은 대상이 Ref_handle 인 경우 카운터를 하나 증가시킨다.
++*rhs.refptr;
// free the left-hand side, destroying pointers if appropriate
// 여기서 만약 자기 자신을 대입할 경우에는 객체의 참조의 카운터가 변화하지 않게된다.
if (--*refptr == 0) {
delete refptr;
delete p;
} // 마지막 대상객체의 핸들이라면 현재 존재하는 대상 객체를 삭제
// copy in values from the right-hand side
refptr = rhs.refptr;
p = rhs.p;
return *this;
}
template <class T> Ref_handle<T>::~Ref_handle()
{
if (--*refptr == 0) {
delete refptr;
delete p;
}
} // 소멸되는 핸들이 대상객체를 가리키는 마지막 객체라면 메모리에서 해제
// refptr도 동적할당된 객체이므로 메모리에서 해제해주는 코드가 필요하다.
#endif
~cpp //Ref_handle을 기반으로 작성된 Student_info 클래스의 사용시 Student_info s1(cin); Student_info s2 = s1; // s1의 값을 s2로 복사한다. 하지만 내부의 객체는 같은 객체를 가리킨다.필요없는 복사는 일어나지 않지만 이 경우 프로그래머가 원치 않을 경우에도 동일한 객체를 가리키는 일이 발생한다.
~cpp
//ptr.h
#ifndef GUARD_Ptr_h
#define GUARD_Ptr_h
#include <cstddef>
#include <stdexcept>
template <class T> class Ptr {
public:
// new member to copy the object conditionally when needed
void make_unique() {
if (*refptr != 1) {
--*refptr;
refptr = new size_t(1);
p = p? clone(p): 0;
}
}
/*
이 함수는 프로그래머가 필요할때 현재 가리키는 내부객체와
동일한 내용의 객체를 복사한 객체를 만들어준다.
대신에 가리키는 대상의 핸들이 1개인 경우에는 이런 복사를 행하지 않는다.
*/
// the rest of the class looks like `Ref_handle' except for its name
Ptr(): p(0), refptr(new size_t(1)) { }
Ptr(T* t): p(t), refptr(new size_t(1)) { }
Ptr(const Ptr& h): p(h.p), refptr(h.refptr) { ++*refptr; }
Ptr& operator=(const Ptr&); // implemented analogously to 14.2/261
~Ptr(); // implemented analogously to 14.2/262
operator bool() const { return p; }
T& operator*() const; // implemented analogously to 14.2/261
T* operator->() const; // implemented analogously to 14.2/261
private:
T* p;
std::size_t* refptr;
};
template <class T> T* clone(const T* tp)
{
return tp->clone();
}
template<class T>
T& Ptr<T>::operator*() const { if (p) return *p; throw std::runtime_error("unbound Ptr"); }
template<class T>
T* Ptr<T>::operator->() const { if (p) return p; throw std::runtime_error("unbound Ptr"); }
template<class T>
Ptr<T>& Ptr<T>::operator=(const Ptr& rhs)
{
++*rhs.refptr;
// \f2free the lhs, destroying pointers if appropriate\fP
if (--*refptr == 0) {
delete refptr;
delete p;
}
// \f2copy in values from the right-hand side\fP
refptr = rhs.refptr;
p = rhs.p;
return *this;
}
template<class T> Ptr<T>::~Ptr()
{
if (--*refptr == 0) {
delete refptr;
delete p;
}
}
#endif
~cpp Student_info s1; s1.read(cin); Student_info s2 = s1; s2.read(cin)그런데 내부객체인 Ptr 핸들은 그 요소를 나타내는 핸들이 오직 1개일 경우가 아니면 대상의 메모리를 해제 하지 않기 때문에 아래와 같은 코드에서 s1, s2의 값의 변화가 상호 영향을 미치지 않는다.
~cpp
void Student_info::regrade(double final, double thesis) {
cp.make_unique();
if(cp)
cp->regrade(final, thesis);
else throw run_time_error("regrade of unknown student");
}
~cpp
//Str.h - 치기가 귀찮아서 그냥 복사함. -_-
#ifndef GUARD_Str_h
#define GUARD_Str_h
#include <algorithm>
#include <iostream>
#include "Ptr.h"
#include "Vec.h"
template<>
Vec<char>* clone(const Vec<char>*);
// does this version work?
class Str {
friend std::istream& operator>>(std::istream&, Str&);
friend std::istream& getline(std::istream&, Str&);
public:
Str& operator+=(const Str& s) {
data.make_unique();
std::copy(s.data->begin(), s.data->end(),
std::back_inserter(*data));
return *this;
}
// interface as before
typedef Vec<char>::size_type size_type;
// reimplement constructors to create `Ptr's
Str(): data(new Vec<char>) { }
Str(const char* cp): data(new Vec<char>) {
std::copy(cp, cp + std::strlen(cp),
std::back_inserter(*data));
}
Str(size_type n, char c): data(new Vec<char>(n, c)) { }
template <class In> Str(In i, In j): data(new Vec<char>) {
std::copy(i, j, std::back_inserter(*data));
}
// call `make_unique' as necessary
char& operator[](size_type i) {
data.make_unique();
return (*data)[i];
}
const char& operator[](size_type i) const { return (*data)[i]; }
size_type size() const { return data->size(); }
typedef char* iterator;
typedef const char* const_iterator;
iterator begin() { return data->begin(); }
const_iterator begin() const { return data->begin(); }
iterator end() { return data->end(); }
const_iterator end() const { return data->end(); }
private:
// store a `Ptr' to a `vector'
Ptr< Vec<char> > data;
};
// as implemented in 12.3.2/216 and 12.3.3/219
std::ostream& operator<<(std::ostream&, const Str&);
Str operator+(const Str&, const Str&);
inline bool operator<(const Str& lhs, const Str& rhs)
{
return std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
}
inline bool operator>(const Str& lhs, const Str& rhs)
{
return std::lexicographical_compare(rhs.begin(), rhs.end(), lhs.begin(), lhs.end());
}
inline bool operator<=(const Str& lhs, const Str& rhs)
{
return !std::lexicographical_compare(rhs.begin(), rhs.end(), lhs.begin(), lhs.end());
}
inline bool operator>=(const Str& lhs, const Str& rhs)
{
return !std::lexicographical_compare(lhs.begin(), lhs.end(), rhs.begin(), rhs.end());
}
inline bool operator==(const Str& lhs, const Str& rhs)
{
return lhs.size() == rhs.size() &&
std::equal(lhs.begin(), lhs.end(), rhs.begin());
}
inline bool operator!=(const Str& lhs, const Str& rhs)
{
return !(lhs == rhs);
}
#endif
~cpp
template<class T> void Ptr<T>::make_unique() {
if(*refptr != 1) {
--*refptr;
refptf = new size_t(1);
p = p? p->clone() : 0;
}
}
이 구현을 위해서는 Vec::clone()가 정의되어 있어야하지만, 이 함수를 정의하게 될 경우 원래 Vec의 구현이 표준 함수 vector의 구현의 부분이라는 가정에서 위배되기 때문에 추가할 수는 없다.~cpp
template<class T> void Ptr<T>::make_unique() {
if(*refptr != 1) {
--*refptr;
refptf = new size_t(1);
p = p? clone() : 0;
}
}
~cpp
template<> Vec<char*> clone(const Vec<char>* vp) {
return new Vec<char>(*vp);
}
템플릿의 구체화(template specialization)| * Ptr<T>::make_unique()를 사용하지 않는다면 T::clone은 불필요 * Ptr<T>::make_unique를 사용한다면 T::clone가 있다면 T::clone을 사용할 것이다. * Ptr<T>::make_unique를 사용한다면 T::clone가 미정의되었다면 clone<T>를 정의해서 원하는 바를 얻을 수 있다. |