~cpp class String { // 표준 문자열 형은 이번 아이템의 참조세기를 갖추고 public: // 있을테지만, 그것을 배제했다고 가정하자 String(const char *value = ""); String& operator=(const String& rhs); ... private: char *data; }; String a, b, c, d, e; a = b = c = d = e = "Hello";
~cpp String& String::operator=(const String& rhs) { if (this == &rhs) return *this; // Item E17 참고 delete [] data; data = new char[strlen(rhs.data) + 1]; strcpy(data, rhs.data); return *this; // Item E15 참고 }
~cpp class String { public: ... // String member들 위치 private: struct StringValue { ... }; // 참조를 세는 인자와, String의 값을 저장. StringValue *value; // 위의 구조체의 값 };
~cpp class String { private: struct StringValue { int refCount; // 참조를 세기위함 카운터 char *data; // 값 포인터 StringValue(const char *initValue); ~StringValue(); }; ... }; // StringValue의 복사 생성자, 초기화 목록으로 refCount 인자 1로 초기화 String::StringValue::StringValue(const char *initValue): refCount(1) { data = new char[strlen(initValue) + 1]; // 새로운 값 할당(아직 참조세기 적용 x strcpy(data, initValue); // 스트링 복사 } // StringValue의 파괴자 String::StringValue::~StringValue() { delete [] data; // 스트링 삭제 }
~cpp class String { public: String(const char *initValue = ""); String(const String& rhs); ... };
~cpp // String의 복사 생성자, 초기화 목록으로 StringValue에 값을 할당한다. String::String(const char *initValue): value(new StringValue(initValue)) {}
~cpp String s("More Effective C++");
~cpp String s1("More Effective C++"); String s2("More Effective C++");
~cpp // String 클래스의 복사 생성자, 초기화 목록으로 StringValue의 포인터를 세팅한다. String::String(const String& rhs): value(rhs.value) { ++value->refCount; // 참조 카운터를 올린다. }
~cpp String s1("More Effective C++"); String s2 = s1;
~cpp class String { public: ~String(); ... }; String::~String() { if (--value->refCount == 0) delete value; // 참조 카운터가 1인 상태 }
~cpp class String { public: String& operator=(const String& rhs); ... };
~cpp s1 = s2;
~cpp String& String::operator=(const String& rhs) { if (value == rhs.value) { // 이미 두 객체가 같은 값을 가리킨다면, return *this; // 특별히 할일은 없다. } if (--value->refCount == 0) { // 현재 값이 자신 외에 아무도 참조하고 delete value; // 있지 않다면 삭제 한다. } value = rhs.value; // 자료를 공유한다. ++value->refCount; // 참조 카운터를 올린다. return *this; // 이건 알아서 --+ }
~cpp class String { public: const char& operator[](int index) const; // const String에 대하여 char& operator[](int index); // non-const String에 대하여 ... };
~cpp const char& String::operator[](int index) const { return value->data[index]; }
~cpp String s; ... cout << s[3]; // 이건 읽기(Read) s[5] = 'x'; // 이건 쓰기(Write)
~cpp char& String::operator[](int index) // non-const operator[] { // if we're sharing a value with other String objects, // break off a separate copy of the value for ourselves // 만약 다른 객체와 자료를 공유하지 않고 있다면, 자료를 노출 시켜도 // 상관이 없다. if (value->refCount > 1) { // if 안쪽은 새로운 객체를 생성해야 할 경우 --value->refCount; // 새로운 객체의 생성을 위해서 현재 참조하는 // 자료의 refCount를 감소 시킨다. value = new StringValue(value->data); // 현재 자료의 사본을 만들어 // 이것을 가리킨다. } // 이제, 아무도 공유하고 있지 않은 StringValue의 객체 // 내부의 문자열의 한 부분을 반환한다. return value->data[index]; }
~cpp String s1 = "Hello"; char *p = &s1[1];
~cpp String s2 = s1;
~cpp p = 'x'; // 이 것은 s1, s2를 모두 변경!
~cpp class String { private: struct StringValue { int refCount; bool shareabl; // 이 인자가 더해 졌다. char *data; StringValue(const char *initValue); ~StringValue(); }; ... }; String::StringValue::StringValue(const char *initValue) : refCount(1), shareable(true) // 초기화시에는 공유를 허락한다. { data = new char[strlen(initValue) + 1]; strcpy(data, initValue); } String::StringValue::~StringValue() { delete [] data; }
~cpp String::String(const String& rhs) { if (rhs.value->shareable) { // 공유를 허락할 경우 value = rhs.value; ++value->refCount; } else { // 공유를 거부할 경우 value = new StringValue(rhs.value->data); } }
~cpp char& String::operator[](int index) { if (value->refCount > 1) { --value->refCount; value = new StringValue(value->data); } value->shareable = false; // 이제 자료의 공유를 금지한다. return value->data[index]; }
~cpp class RCObject { public: RCObject(); RCObject(const RCObject& rhs); RCObject& operator=(const RCObject& rhs); virtual ~RCObject() = 0; void addReference(); void removeReference(); void markUnshareable(); bool isShareable() const; bool isShared() const; private: int refCount; bool shareable; };
~cpp RCObject::RCObject() : refCount(0), shareable(true) {} RCObject::RCObject(const RCObject&) : refCount(0), shareable(true) {} RCObject& RCObject::operator=(const RCObject&) { return *this; } RCObject::~RCObject() {} // 파괴자는 그것이 순수 가상 함수라도 // 항상 구현되어 있어야 한다. void RCObject::addReference() { ++refCount; } void RCObject::removeReference() { if (--refCount == 0) delete this; } void RCObject::markUnshareable() { shareable = false; } bool RCObject::isShareable() const { return shareable; } bool RCObject::isShared() const { return refCount > 1; }
~cpp sv1 = sv2; // 어떻게 sv1과 sv2의 참조 카운터가 영향 받을까?
~cpp class String { private: struct StringValue: public RCObject { char *data; StringValue(const char *initValue); ~StringValue(); }; ... }; String::StringValue::StringValue(const char *initValue) { data = new char[strlen(initValue) + 1]; strcpy(data, initValue); } String::StringValue::~StringValue() { delete [] data; }
~cpp class String { private: struct StringValue: public RCObject { ... }; StringValue *value; // 이 String의 값 ... };
~cpp // T 객체를 가리키기 위한 스마트 포인터 클래스 템플릿 // 여기에서 T는 RCObject나, 이것을 상속 받는 클래스이다. template<class T> class RCPtr { public: RCPtr(T* realPtr = 0); RCPtr(const RCPtr& rhs); ~RCPtr(); RCPtr& operator=(const RCPtr& rhs); T* operator->() const; // Item 28 참고 T& operator*() const; // Item 28 참고 private: T *pointee; // 원래의 포인터를 가리키기위한 // 더미(dumb) 포인터 pointer this void init(); // 보통의 초기화 루틴 };
~cpp template<class T> RCPtr<T>::RCPtr(T* realPtr): pointee(realPtr) { init(); } template<class T> RCPtr<T>::RCPtr(const RCPtr& rhs): pointee(rhs.pointee) { init(); } template<class T> void RCPtr<T>::init() { if (pointee == 0) { // 만약 더미(dumb)포인터가 null이라면 return; } if (pointee->isShareable() == false) { // 공유를 허용하지 않으면 pointee = new T(*pointee); // 복사를 한다. } pointee->addReference(); // 새로운 참조 카운터를 올린다. }
~cpp pointee = new T(*pointee);
~cpp class String { private: struct StringValue: public RCObject { StringValue(const StringValue& rhs); ... }; ... }; String::StringValue::StringValue(const StringValue& rhs) { data = new char[strlen(rhs.data) + 1]; // 이 부분이 deep copy의 strcpy(data, rhs.data); // 수행 부분이다. }
~cpp class String { private: struct StringValue: public RCObject { ... }; struct SpecialStringValue: public StringValue { ... }; ... };
~cpp pointee = new T(*pointee); // T 는 StringValue이다, 하지만 // pointee가 가리키는건 SpecialStringVale이다.
~cpp template<class T> RCPtr<T>& RCPtr<T>::operator=(const RCPtr& rhs) { if (pointee != rhs.pointee) { // 둘이 같은 객체를 공유하면 // 할당을 생략한다. if (pointee) { pointee->removeReference(); // 현재 참조 객체 참조 카운터 감소 or 파괴 } pointee = rhs.pointee; // 가리키는 참조를 옮긴다. init(); // 공유가 불가능 할경우는 자료를 // 생성하고, 어찌되었든 참조를 증가 } return *this; }
~cpp template<class T> RCPtr<T>::~RCPtr() { if (pointee)pointee->removeReference(); // 알아서 파괴됨 }
~cpp template<class T> T* RCPtr<T>::operator->() const { return pointee; } template<class T> T& RCPtr<T>::operator*() const { return *pointee; }
~cpp template<class T> // T를 가리키는 스마트 포인터 class RCPtr { // 여기에서 T는 RCObject를 상속해야 한다. public: RCPtr(T* realPtr = 0); // 초기화, 생성자 겸(기본값 때문에) RCPtr(const RCPtr& rhs); // 복사 생성자 ~RCPtr(); RCPtr& operator=(const RCPtr& rhs); // 할당 연산자 T* operator->() const; // 포인터 흉내 T& operator*() const; // 포인터 흉내 private: T *pointee; // 더미(dumb) 포인터 void init(); // 참조 세기 초기화(참조 카운터 증가) }; class RCObject { // 참조세기의 기초 클래스 public: void addReference(); // 참조 카운터 증가 void removeReference(); // 참조 카운터 감소 void markUnshareable(); // 공유 막기 bool isShareable() const; // 공유 해도 되는가 묻기 bool isShared() const; // 현재 공유 중인가 묻기 protected: RCObject(); // 생성자 RCObject(const RCObject& rhs); // 복사 생성자 RCObject& operator=(const RCObject& rhs); // 할당 연산자 virtual ~RCObject() = 0; // 순수 가상 파괴자 // (순수지만 반드시 구현되어야 한다.) private: int refCount; // 참조 카운터 bool shareable; // 공유 플래스(공유 허용 여부) }; // 처음 보다 많이 빠진 String같지 않은가? ^^; class String{ // application 개발자가 사용하는 클래스 public: String(const char *value = ""); // 생성자, 초기화 const char& operator[](int index) const; // const operator[] char& operator[](int index); // non-const operator[] private: // 클래스 내부에 표현을 위한 문자열 값 struct StringValue: public RCObject { char *data; // 데이터 포인터 StringValue(const char *initValue); // 생성자 StringValue(const StringValue& rhs); // 복사 생성자 void init(const char *initValue); // 참조 세팅 ~StringValue(); // 파괴자 }; RCPtr<StringValue> value; // StringValue의 스마트 포인터 };
~cpp RCObject::RCObject() : refCount(0), shareable(true) {} RCObject::RCObject(const RCObject&) : refCount(0), shareable(true) {} RCObject& RCObject::operator=(const RCObject&) { return *this; } RCObject::~RCObject() {} void RCObject::addReference() { ++refCount; } void RCObject::removeReference() { if (--refCount == 0) delete this; } void RCObject::markUnshareable() { shareable = false; } bool RCObject::isShareable() const { return shareable; } bool RCObject::isShared() const { return refCount > 1; }
~cpp template<class T> void RCPtr<T>::init() { if (pointee == 0) return; if (pointee->isShareable() == false) { pointee = new T(*pointee); } pointee->addReference(); } template<class T> RCPtr<T>::RCPtr(T* realPtr) : pointee(realPtr) { init(); } template<class T> RCPtr<T>::RCPtr(const RCPtr& rhs) : pointee(rhs.pointee) { init(); } template<class T> RCPtr<T>::~RCPtr() { if (pointee)pointee->removeReference(); } template<class T> RCPtr<T>& RCPtr<T>::operator=(const RCPtr& rhs) { if (pointee != rhs.pointee) { if (pointee) pointee->removeReference(); pointee = rhs.pointee; init(); } return *this; } template<class T> T* RCPtr<T>::operator->() const { return pointee; } template<class T> T& RCPtr<T>::operator*() const { return *pointee; }
~cpp void String::StringValue::init(const char *initValue) // deep copy를 위해서 { data = new char[strlen(initValue) + 1]; // 자료를 복사하는 strcpy(data, initValue); // 과정 개발자가 신경써야 한다. } String::StringValue::StringValue(const char *initValue) // 복사 생성자(자료 기반) { init(initValue); } String::StringValue::StringValue(const StringValue& rhs)// 복사 생성자(같은 객체 기반) { init(rhs.data); } String::StringValue::~StringValue() { delete [] data; }
~cpp String::String(const char *initValue) : value(new StringValue(initValue)) {} // value는 RCPtr<StringValue> 이다. const char& String::operator[](int index) const { return value->data[index]; } char& String::operator[](int index) { if (value->isShared()) { value = new StringValue(value->data); } value->markUnshareable(); return value->data[index]; }
~cpp template<class T> class RCIPtr { public: RCIPtr(T* realPtr = 0); RCIPtr(const RCIPtr& rhs); ~RCIPtr(); RCIPtr& operator=(const RCIPtr& rhs); const T* operator->() const; // 설명과 구현 코드를 보자. T* operator->(); // '' const T& operator*() const; // '' T& operator*(); // '' private: struct CountHolder: public RCObject { ~CountHolder() { delete pointee; } T *pointee; }; CountHolder *counter; void init(); void makeCopy(); // 구현 코드를 보자. }; template<class T> void RCIPtr<T>::init() { if (counter->isShareable() == false) { T *oldValue = counter->pointee; counter = new CountHolder; counter->pointee = new T(*oldValue); } counter->addReference(); } template<class T> RCIPtr<T>::RCIPtr(T* realPtr) : counter(new CountHolder) { counter->pointee = realPtr; init(); } template<class T> RCIPtr<T>::RCIPtr(const RCIPtr& rhs) : counter(rhs.counter) { init(); } template<class T> RCIPtr<T>::~RCIPtr() { counter->removeReference(); } template<class T> RCIPtr<T>& RCIPtr<T>::operator=(const RCIPtr& rhs) { if (counter != rhs.counter) { counter->removeReference(); counter = rhs.counter; init(); } return *this; } template<class T> void RCIPtr<T>::makeCopy() // copy-on-write 상황의 구현 { if (counter->isShared()) { T *oldValue = counter->pointee; counter->removeReference(); counter = new CountHolder; counter->pointee = new T(*oldValue); counter->addReference(); } } template<class T> // const 접근; const T* RCIPtr<T>::operator->() const // copy-on-write를 비감안할 { return counter->pointee; } template<class T> // non-const 접근 T* RCIPtr<T>::operator->() // copy-on-write 감안 { makeCopy(); return counter->pointee; } template<class T> // const 접근; const T& RCIPtr<T>::operator*() const // copy-on-write를 비감안할 { return *(counter->pointee); } template<class T> // non-const 접근 T& RCIPtr<T>::operator*() // copy-on-write 감안 { makeCopy(); return *(counter->pointee); }
~cpp class Widget { public: Widget(int size); Widget(const Widget& rhs); ~Widget(); Widget& operator=(const Widget& rhs); void doThis(); int showThat() const; };
~cpp class RCWidget { public: RCWidget(int size): value(new Widget(size)) {} void doThis() { value->doThis(); } // delegate시켜 준다. int showThat() const { return value->showThat(); } // delegate시켜 준다. private: RCIPtr<Widget> value; };
~cpp int data[10][20]; // 2차원 배열 10 by 20
~cpp void processInput(int dim1, int dim2) { int data[dim1][dim2]; // 에러! 배열의 차원은 단지 컴파일 중에만 결정된다. ... }
~cpp int *data = new int[dim1][dim2]; // 에러!
~cpp template<class T> class Array2D { public: Array2D(int dim1, int dim2); ... };
~cpp Array2D<int> data(10, 20); // 옳다 Array2D<float> *data = new Array2D<float>(10, 20); // 옳다 void processInput(int dim1, int dim2) { Array2D<int> data(dim1, dim2); // 옳다 ... }
~cpp cout << data[3][6];
~cpp template<class T> class Array2D { public: // 이러한 선언은 컴파일 할수 없다. T& operator[][](int index1, int index2); const T& operator[][](int index1, int index2) const; ... };
~cpp class Array2D { public: // 이런 사항은 잘 컴파일 된다. T& operator()(int index1, int index2); const T& operator()(int index1, int index2) const; ... };
~cpp cout << data(3, 6);
~cpp int data[10][20]; ... cout << data[3][6];
~cpp template<class T> class Array2D { public: // 2번째 차원에 위치하는 Array1D class Array1D { public: T& operator[](int index); const T& operator[](int index) const; ... }; // 위의 1차원 배열 객체 Array1D Array1D operator[](int index); const Array1D operator[](int index) const; ... };
~cpp Array2D<float> data(10, 20); ... cout << data[3][6]; // 옳다.
~cpp String s1, s2; // 문자열과 비슷한 클래스;프록시의 쓰임은 // 표준 문자열 인터페이스를 따르는 클래스 형태를 // 유지한다. ... cout << s1[5]; // s1 읽기 s2[5] = 'x'; // s2 쓰기 s1[3] = s2[8]; // s1 쓰기, s2 읽기
~cpp class String { public: const char& operator[](int index) const; // 읽기 위해 존재 char& operator[](int index); // 쓰기 위해 존재 ... };
~cpp String s1, s2; ... cout << s1[5]; // non-const operator[] 호출 왜냐하면 // s1이 non-const이기 때문에 s2[5] = 'x'; // 역시 non-const operator[] 호출: s2는 non-const이다. s1[3] = s2[8]; // 둘다 non-const operator[] 호출 왜냐하면 s1,s2모두 // non-const 객체이다.
~cpp class String { // 참조세기가 적용된 문자열, Item 29참고 public: class CharProxy { // 문자의 프록시 public: CharProxy(String& str, int index); // 생성 CharProxy& operator=(const CharProxy& rhs); // lvalue CharProxy& operator=(char c); // 의 쓰임에 반응 operator char() const; // rvalue의 쓰임에 반응 // use private: String& theString; // 프록시에서 문자열을 참조할 경우가 필요할시 int charIndex; // 문자의 인덱스 }; // String클래스가 포함하고 있는 것 const CharProxy operator[](int index) const; // const String에 반응 CharProxy operator[](int index); // non-const String을 위해서 ... friend class CharProxy; private: RCPtr<StringValue> value; };
~cpp String s1, s2; // 프록시를 사용하는 참조 세기가 적용된 문자열 ... cout << s1[5]; // 옳다. s2[5] = 'x'; // 역시 옳다. s1[3] = s2[8]; // 역시나 잘돌아간다.
~cpp cout << s1[5];
~cpp s2[5] = 'x';
~cpp s1[3] = s2[8];
~cpp const String::CharProxy String::operator[](int index) const { return CharProxy(const_cast<String&>(*this), index); } String::CharProxy String::operator[](int index) { return CharProxy(*this, index); }
~cpp String::CharProxy::CharProxy(String& str, int index) : theString(str), charIndex(index) {}
~cpp String::CharProxy::operator char() const { return theString.value->data[charIndex]; }
~cpp String::CharProxy& String::CharProxy::operator=(const CharProxy& rhs) { // 만약 문자열이 다른 String객체와 값을 공유가 가능할 경우 if (theString.value->isShared()) { theString.value = new StringValue(theString.value->data); } // 문자 값의 복사 과정 theString.value->data[charIndex] = rhs.theString.value->data[rhs.charIndex]; return *this; }
~cpp The second CharProxy assignment operator is almost identical: ¤ Item M30, P58 String::CharProxy& String::CharProxy::operator=(char c) { if (theString.value->isShared()) { theString.value = new StringValue(theString.value->data); } theString.value->data[charIndex] = c; return *this; }
~cpp String s1 = "Hello"; char *p = &s1[1]; // 에러!
~cpp class String { public: class CharProxy { public: ... char * operator&(); const char * operator&() const; ... }; ... };
~cpp const char * String::CharProxy::operator&() const { return &(theString.value->data[charIndex]); }
~cpp char * String::CharProxy::operator&() { // 다른 객체와 정보를 공유할때는 새 자료를 만든다. if (theString.value->isShared()) { theString.value = new StringValue(theString.value->data); } // 이제 이 함수가 반환하는 객체를 통하여 수정할수 있다. 그러므로 // 공유 못하도록 막는다. theString.value->markUnshareable(); return &(theString.value->data[charIndex]); }
~cpp template<class T> // 프록시를 사용하는 class Array { // 참조세기 적용 배열 public: class Proxy { public: Proxy(Array<T>& array, int index); Proxy& operator=(const T& rhs); operator T() const; ... }; const Proxy operator[](int index) const; Proxy operator[](int index); ... };
~cpp Array<int> intArray; ... intArray[5] = 22; // 옳다. intArray[5] += 5; // 에러! ++intArray[5]; // 에러!
~cpp class Rational { public: Rational(int numerator = 0, int denominator = 1); int numerator() const; int denominator() const; ... }; Array<Rational> array;
~cpp cout << array[4].numerator(); // 에러! int denom = array[22].denominator(); // 에러!
~cpp void swap(char& a, char& b); // a 와 b의 값을 바꾼다. String s = "+C+"; // 에구, 이거 이 문자열은 "C++" 일텐데. swap(s[0], s[1]); // 그것을 고칠려고 한다.
~cpp class TVStation { public: TVStation(int channel); ... }; void watchTV(const TVStation& station, float hoursToWatch);
~cpp watchTV(10, 2.5); // 10번 본다. 2.5시간 본다.
~cpp Array<int> intArray; intArray[4] = 10; watchTV(intArray[4], 2.5);