~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);