~cpp #define ASPECT_RATIO 1.653ASPECT_RATIO는 소스코드가 컴파일로 들어가기 전에 전처리기에 의해 제거된다.
~cpp const double ASPECT_RATIO = 1.653책에서 언급한 두가지.
~cpp 1. 상수 포인터(constant pointer)를 정의하기가 다소 까다로워 진다는 것. - ex - const char * const authorName = "Scott Meyers"; 2. 상수의 영역을 클래스로 제한하기 위해선 상수를 멤버로 만들어야 하며 그 상수에 대한 단 한개의 복사본이 있다는 것을 확신하기 위해서 static으로 멤버 변수를 만들어야 한다. - ex - // header. class GamePlayer { private: static const int NUM_TURNS = 5; // 상수 선언! (선언만 한것임) int scores[NUM_TURNS]; // 상수의 사용. } // source file ... const int GamePlayer::NUM_TURNS; // 정의를 꼭해주어야 한다. ...
~cpp - ex - #define max(a,b) ((a) > (b) ? (a) : (b)) // 매크로 작성시에는 언제나 매크로 몸체의 모든 인자들을 괄호로 묶어 주어야 한다. // 왜인지는 다들 알것이다. // #define 을 inline으로.. inline int max(int a, int b) { return a > b ? a : b; } // int형으로만 제한 되어있네.. // template으로 template class<T> inline const T& max (const T& a, const T& b) { return a > b ? a : b; }const와 inline을 쓰자는 얘기였습니다. --; 왜 그런지는 아시는 분께서 글좀 남기시구요.
~cpp string *stringArray = new string[100]; ... delete stringArray; // delete를 잘못 써주었습니다. // stringArray에 의해 가르켜진 100개의 string object들중에 99개는 제대로 제거가 안됨.
~cpp typedef string AddressLines[4]; // 개인 주소는 4개의 줄을 차지하고 // 각각이 스트링이다. ... string *pal = new AddressLines; // "new AddressLines" returns a string *, just "new string[4]".. ... delete pal; // 어떻게 될지 몰라~ delete [] pal; // fine.이런 혼란(?)을 피하기 위해선 배열 타입들에 대한 typedef를 피하면 되지뭐.
~cpp typedef void (* new_handler) {}; // 함수 pointer new_handler set_new_handler (new_handler p) throw (); ... // 연산자 new가 충분한 메모리를 할당하지 못할 경우 호출될 함수 void noMoreMemory () { cerr << "Unable to satisfy request for memory\n"; abort (); } ... void main () { set_new_handler (noMoreMemory); int *pVigdataArray = new int [100000000]; // 100000000개의 정수공간을 할당할 수 없다면 noMoreMemory가 호출. ... }
~cpp class X { public: static new_handler set_new_handler(new_handler p); static void * operator new(size_t size); private: static new_handler currentHandler; }; ... // source file (??.cpp) new_handler X::currentHandler; // sets currentHandler // to 0 (i.e., null) by // default new_handler X::set_new_handler(new_handler p) { new_handler oldHandler = currentHandler; currentHandler = p; return oldHandler; } void * X::operator new(size_t size) { new_handler globalHandler = // install X's std::set_new_handler(currentHandler); // handler void *memory; try { // attempt memory = ::operator new(size); // allocation } catch (std::bad_alloc&) { // restore std::set_new_handler(globalHandler); // handler; throw; // propagate } // exception std::set_new_handler(globalHandler); // restore // handler return memory; } ... void noMoreMemory(); // decl. of function to // call if memory allocation // for X objects fails ... X::set_new_handler(noMoreMemory); // set noMoreMemory as X's // new-handling function X *px1 = new X; // if memory allocation // fails, call noMoreMemory string *ps = new string; // if memory allocation // fails, call the global // new-handling function // (if there is one) X::set_new_handler(0); // set the X-specific // new-handling function // to nothing (i.e., null) X *px2 = new X; // if memory allocation // fails, throw an exception // immediately. (There is // no new-handling function // for class X.)내 생각에는 이런게 있다라고만 알아두면 좋을것 같다. --;
~cpp // operator new void * operator new (size_t size) { if (size == 0) { // handle 0-byte requests size = 1; // by treating them as } // 1-byte requests while (1) { // size bytes를 할당.. if (the allocation was successful) return (a pointer to the memory); new_handler globalHandler = set_new_handler(0); set_new_handler(globalHandler); if (globalHandler) (*globalHandler)(); else throw std::bad_alloc(); } }operator new 가 하부 클래스로 상속된다면 어떻게 될까?
~cpp // in class class Base { public: static void * operator new(size_t size); ... }; class Derived: public Base // Derived doesn't declare { ... }; // operator new ... Derived *p = new Derived; // calls Base::operator new! // 만일 Base의 operator new가 이에 대처하기 위해 설계되지 않았다면 그를 // 위한 최선의 방법은 다음과 같이 "잘못된" 양의 메모리를 요청하고 있는 // 호출들을 표준 operator new로 전달하는 것이다 void *Base::operator new (size_t size) { if (size != sizeof (Base)) // size가 잘못 되었으면 return ::operator new (size); // 요구를 처리한다 ... // 그렇지 않으면 여기서 요구를 처리함 }멤버가 아닌 operator delete
~cpp // operator delete void operator delete(void *rawMemory) { if (rawMemory == 0) return; // do nothing if the null // pointer is being deleted // deallocate the memory pointed to by rawMemory; return; }이 연산자도 역시 상속될 경우 약간 골치아픈가?
~cpp // in class class Base { // same as before, but now public: // op. delete is declared static void * operator new(size_t size); static void operator delete(void *rawMemory, size_t size); ... }; void Base::operator delete(void *rawMemory, size_t size) { if (rawMemory == 0) return; // check for null pointer if (size != sizeof(Base)) { // if size is "wrong," ::operator delete(rawMemory); // have standard operator return; // delete handle the request } // deallocate the memory pointed to by rawMemory; return; }
~cpp class X { public: void f(); // new 핸들링 함수의 사양을 만족하는 연산자 new static void * operator new(size_t size, new_handler p); }; void specialErrorHandler(); // definition is elsewhere X *px1 = new (specialErrorHandler) X; // calls X::operator new X *px2 = new X; // error!, "정상 form에 대해 호환이 이루어 지지않는 문제점."위의 문제를 해결하기 위해.
~cpp class X { public: void f(); static void * operator new(size_t size, new_handler p); static void * operator new(size_t size) // normal form형식의 연산자도 만들어준다. { return ::operator new(size); } }; X *px1 = new (specialErrorHandler) X; // calls X::operator // new(size_t, new_handler) X* px2 = new X; // calls X::operator // new(size_t)or
~cpp class X { public: void f(); static void * operator new(size_t size, new_handler p = 0); // default 값을 주어서 처리해준다 }; X *px1 = new (specialErrorHandler) X; // ok X* px2 = new X; // ok어떤 방법이든 상관 없지만, code를 약간이라도 덜치는 defaut 인자를 주는것이.. ㅡㅡ;; 하하
~cpp // 완벽하지 않은 String class class String { public: String(const char *value); ~String(); private: char *data; }; String::String(const char *value) { if (value) { data = new char[strlen(value) + 1]; strcpy(data, value); } else { data = new char[1]; *data = '\0'; } } inline String::~String() { delete [] data; }
~cpp b = a;클래스 내에 operator=가 정의 되어 있지 않기 때문에, C++에서 default 치환 연산자를 호출한다.
~cpp String a("Hello"); // a를 생성 ... { // 새로운 영역 String b("World"); // b를 생성 ... b = a; // default 치환 연산자 수행 // b의 메모리를 잃게 된다. } // 영역이 닫힌후, // b의 소멸자가 호출된다. 그러므로, a가 가리키던 data도 소멸되게 된다. String c = a; // c의 data는 정의 되지 않는다. // 복사 생성자가 정의 되지 않았기 때문에 C++에서 제공하는 default 치환 연산자 호출. // a의 data는 이미 지워 졌기 때문에 memory leak의 문제는 없다. // 그러나, c와 a는 같은 곳을 가리킨다. 그리고, c의 소멸자가 호출 되면 위에서 삭제된 곳을 다시한번 // 삭제 하게 된다. 결과 적으로 a와 c가 가리키던 곳이 두번 삭제 되는 경우가 발생된다. (a가 소멸할때, c가 소멸할때)이상 치환 연산자에 관한것.
~cpp void doNothing(String localString) {} ... String s = "The Truth Is Out There"; doNothing(s); // deault 복사 생성자 호출. call-by-value로 인해 // localString은 s안에 있는 포인터에 대한 복사본을 가지게 된다. // 그래서, doNothing이 수행을 마치면, localString은 여역을 벗어나고, 소멸자가 호출된다. // 그 결과 s는 localString이 삭제한 메모리에 대한 포인터를 가지게 된다. (data 손실)* 클래스 안에 포인터를 조물딱 거리는 멤버 변수가 있을 경우에는 그 클래스에 복사 생성자와, 치환 연산자를 꼭 정의해 주어야 한다...
~cpp template<class T> class NamedPtr { public: NamedPtr(const string& initName, T *initPtr); ... private: string name; T *ptr; };
~cpp template<class T> NamedPtr<T>::NamedPtr(const string& initName, T *initPtr ) : name(initName), ptr(initPtr) {}2. 생성자의 코드 부분에서 치환을 한다.
~cpp template<class T> NamedPtr<T>::NamedPtr(const string& initName, T *initPtr) { name = initName; ptr = initPtr; }2가지 방법 정도로 멤버 변수를 초기화 할수 있는데. 책에서는 초기화 리스트를 선호한다.
~cpp class Wacko { public: Wacko(const char *s): s1(s), s2(0) {} Wacko(const Wacko& rhs): s2(rhs.s1), s1(0) {} private: string s1, s2; }; Wacko w1 = "Hello world!"; Wacko w2 = w1;w1과 w2의 멤버들은 다른 순서에 따라 생성될 것이다. 그리고, 다시 그 객체들(string 객체)을 소멸하기 위해서
~cpp // base class class EnemyTarget { public: EnemyTarget() { ++numTargets; } EnemyTarget(const EnemyTarget&) { ++numTargets; } ~EnemyTarget() { --numTargets; } static unsigned int numberOfTargets() { return numTargets; } virtual bool destroy(); // EnemyTarget 객체 파괴에 // 성공하면 참을 돌려 준다 private: static unsigned int numTargets; // 객체 카운터 }; // class내의 정적 변수는 클래스의 바깥쪽에 정의되어야 한다. // 기본적으로 0으로 초기화된다. unsigned int EnemyTarget::numTargets; ... .. // base class를 상속한 클래스 class EnemyTank: public EnemyTarget { public: EnemyTank() { ++numTanks; } EnemyTank(const EnemyTank& rhs) : EnemyTarget(rhs) { ++numTanks; } ~EnemyTank() { --numTanks; } static unsined int numberOfTanks() { return numTanks; } virtual bool destroy(); private: static unsigned int numTanks; // object counter for tanks }; unsigned int EnenyTank::numTanks;EnemyTarget의 객체를 카운트 하기 위해 정적 멤버 변수 numTargets를 두었으며 EnemyTarget을 상속한 EnemyTank에서도
~cpp EnemyTarget *targetPtr = new EnemyTank; ... delete targetPtr; // 아무 문제가 없어 보인다.The C++ language standard is unusually clear on this topic. 베이스 클래스에 대한 포인터를 사용해서 계승된 클래스를
~cpp w = x= y = z = "Hello";이 런식의 연속적인 치환 연산을 할 수 있어야 한다. 그렇기 때문에 operator=연산자의 리턴형을 void로 정의 하면 안된다.
~cpp class Widget { public: ... // note const Widget& operator=(const Widget& rhs); // const ... // return }; // type ... Widget w1, w2, w3; ... (w1 = w2) = w3; // assign w2 to w1, then w3 to // the result! (Giving Widget's // operator= a const return value // prevents this from compiling.)그래서, operator=의 리턴형을 const로 작성하면 안된다. (....--;...)
~cpp String& String::operator=(const String& rhs) { ... return *this; // return reference // to left-hand object } String& String::operator=(const String& rhs) { ... return rhs; // return reference to // right-hand object }위의 두가지 경우는 별다른 차이가 없어보인다. 그러나, 중요한 차이점이 있으니 말을 꺼내는 것이 겠지? --;
~cpp String& String::operator=(String& rhs) { ... }하지만, 이번에는 클래스의 operator=를 사용하는 코드에서 문제가 발생한다.
~cpp x = "Hello";치환의 오른쪽 부분이 String형이 아니라 char *형이기 때문에 컴파일러는 String의 생성자를 통해 임시 String객체를 만들어서 호출을 한다. 즉, 아래와 같은 code를 생성한다.
~cpp const String temp("Hello"); // 임시 객체를 만든다. ... x = temp; // 임시 객체를 operator=에 전달한다.컴파일러는 위와 같은 임시 객체를 만들려고 하지만, 임시 객체가 const라는 것에 주의. 그리고, operator=의 리턴형을 보면 String에 대한 레퍼런스를 돌려주기 때문에 리턴형이 일치하지 않게 된다. 그래서, error를 발생시킨다. 만약 error를 발생 시키지 않는다면, operator=이 호출되는 측에서 제공된 인자가 아니라 컴파일러가 발생시킨 임시 변수만 수정된다는 것에 놀랄것이다. --;
~cpp class Base { public: Base(int initialValue = 0): x(initialValue) {} private: int x; }; class Derived: public Base { public: Derived(int initialValue) : Base(initialValue), y(initialValue) {} Derived& operator=(const Derived& rhs); private: int y; }; // The logical way to write Derived's assignment operator is like this // erroneous assignment operator Derived& Derived::operator=(const Derived& rhs) { if (this == &rhs) return *this; y = rhs.y; // assign to Derived's // lone data member return *this; // see Item 15 } // Unfortunately, this is incorrect, because the data member x in // the Base part of a Derived object is unaffected by this assignment operator. // For example, consider this code fragment void assignmentTester() { Derived d1(0); // d1.x = 0, d1.y = 0 Derived d2(1); // d2.x = 1, d2.y = 1 d1 = d2; // d1.x = 0, d1.y = 1! }보기와 같이 제대로 작동하지 않는 operator= 연산자이다. 그럼, 이것을 어떻게 고치면 좋을까? 이 문제를 해결하기 위해서는, 다음과 같이 Base클래스의 operator=연산자를 호출해 주면 된다. ( Derived 클래스의 operator= 연산자에서 x를 치환해 준다는 것은 허용되지 않기 때문에.)
~cpp // correct assignment operator Derived& Derived::operator=(const Derived& rhs) { if (this == &rhs) return *this; Base::operator=(rhs); // call this->Base::operator= y = rhs.y; return *this; }
~cpp class Base { public: Base(int initialValue = 0): x(initialValue) {} Base(const Base& rhs): x(rhs.x) {} private: int x; }; class Derived: public Base { public: Derived(int initialValue) : Base(initialValue), y(initialValue) {} Derived(const Derived& rhs) // erroneous copy : y(rhs.y) {} // constructor private: int y; };Derived 클래스의 복사 생성자를 보면 Base클래스의 멤버 변수는 초기화 시키지 않음을 알수 있다. 이런 문제를 피하기 위해서는 밑의 코드와 같이 Base클래스의 복사 생성자를 호출해 주면 된다.
~cpp class Derived: public Base { public: Derived(const Derived& rhs): Base(rhs), y(rhs.y) {} ... };이젠 Derived클래스의 복사생성자를 호출해도 Base클래스의 멤버 변수역시 잘 복사 된다.
~cpp class X { ... }; X a; a = a; // a is assigned to itself'왜 이런 대입을 하는거지. 프로그램 짜는 놈이 바보 인가?' 라는 생각을 할 수 도있지만, 밑의 코드가 있다고 하자.
~cpp a = b그런데 b가 a로 초기화된 레퍼런스라면 보기에는 재귀치환에 해당한다. 이런 가능한 상황에 대처하기 위해 특별히 주의를 가지는 것에 는 두가지 좋은 점이 있다. 첫째는 효율성이다. 치환 연산자의 상위 부분에서 재귀치환을 검사할 수 있다면, 바로 리턴할 수 있기 때문이다. 두번째는, 정확함을 확인하는 것이다. 일반적으로 치환 연산자는 객체의 새로운 값에 해당하는 새로운 리소스들을 할당하기 전에 객체에 할당된 리소스들을 해제해야만 한다. 이전 값들을 제거해야 한다는 말이다. 재귀치환일 경우 이런식으로 이전 값들을 제거할경우 큰 hazard를 가져 온다. 왜냐하면, 기존 리소스들이 새로운 리소들을 치환하는 과정에서 필요하게 될 수 있기 때문이다.
~cpp class String { public: String(const char *value); // see Item 11 for // function definition ~String(); // see Item 11 for // function definition ... String& operator=(const String& rhs); private: char *data; }; // an assignment operator that omits a check // for assignment to self String& String::operator=(const String& rhs) { delete [] data; // delete old memory // allocate new memory and copy rhs's value into it data = new char[strlen(rhs.data) + 1]; strcpy(data, rhs.data); return *this; // see Item 15 }그리고, 이와 같은 경우를 보자.
~cpp String a = "Hello"; a = a; // same as a.operator=(a)String객체의 operator= 연산자를 볼때 *this와 rhs는 다른것 처럼 보이지만, 이 둘은 같은 데이터를 pointing하고 있다. (같은 객체이기 때문에..)
~cpp // data의 동일성을 검사 String& String::operator=(const String& rhs) { if (strcmp(data, rhs.data) == 0) return *this; ... } // 객체의 멤버 변수들의 값이 동일한지 검사 // 이때 operator== 을 다시 재정의 해주어야 한다 C& C::operator=(const C& rhs) { // check for assignment to self if (*this == rhs) // assumes op== exists return *this; ... } // 주소값을 통한 검사 - 가장 좋은 효과를 기대할만 하다. C& C::operator=(const C& rhs) { // check for assignment to self if (this == &rhs) return *this; ... } // 클래스마다 식별자를 두어 검사하는 방법 // 이경우에도 operator == 을 정의하여 식별자가 같은지 검사해보아야 한다. class C { public: ObjectID identity() const; // see also Item 36 ... };
~cpp Num a(10); int temp; temp = 2 * a; // 이런 연산. - friend함수를 사용하여 연산자를 정의 해줘야지만 작동한다.그렇지만, 이런 연산자들을 거의 안쓰는 것같다.. ㅡㅡ; 나도 friend함수 써본 일이 없다.. ㅡㅡ; 학교 시험에서 나올법한 얘기들.
~cpp const char *p = "Hello"; char * const p = "Hello"; const char * const p = "Hello"; // 이 세가지의 차이점은 뭘까요? // 잘 생각 하면 알수 있을 거에요. // pointer란 가리키는 곳의 값과, 주소로 이루어 진 놈이니까. // 어떤때는 주소가 변하지 말았으면 할때이고, // 어떤놈은 가리키는 곳의 값이 변하지 말았으면 할때.. 이런 식으로 생각하면 쉬울듯 하네요.. ㅎㅎ.. 추후에 정리..