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>를 정의해서 원하는 바를 얻을 수 있다. |