~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란 가리키는 곳의 값과, 주소로 이루어 진 놈이니까. // 어떤때는 주소가 변하지 말았으면 할때이고, // 어떤놈은 가리키는 곳의 값이 변하지 말았으면 할때.. 이런 식으로 생각하면 쉬울듯 하네요.. ㅎㅎ.. 추후에 정리..