friend class가 마치 OOP를 해칠 것만 같은 느낌을 주지만 그렇지 않다. 그 이유는 2개다. 먼저 friend 관계는 외부에 노출되지 않는다. 두번째로 friend라고 해도 모든 friend 클래스에게 전적으로 멤버를 공개하는 것이나 아니라 friend 클래스의 특정 함수에게만 공개할 수도 있다. 즉, friend 클래스도 결국은 interface로서 작동할 수 있다.
그런데 직접 리모컨 클래스를 구현해보니, 왠만한 경우에 Tv 클래스의 public 멤버만 쓴다는 것을 깨달았다. 실제로 Tv 클래스의 Private 멤버를 쓰는 일은 리모컨의 set_channel()에서만 일어났다. 이 경우에 Friend Member Functions을 지시할 수 있다.
classTv
{public:
friendvoidRemote::set_channel(Tv & t, int c);
...
}
이 때, 실제로 코드를 써보면 에러를 맞닥뜨리게 된다. Tv 클래스와 Remote 클래스의 정의 순서 때문이다.
// 1. TV class가 friend member function에서// Remote를 참조하기 때문에 Remote가 클래스임을 컴파일러에게 알려줘야 한다.// 2. Remote class도 멤버 함수에서 TV 클래스를 쓰기 때문에 TV보다 아래에 있어야 한다.classTV
{
...
}
classRemote
{
...
}
실제로 꽤 쓸만해보이는 원칙이 현장에선 먹히지 않는 법이다. C++98의 exception specification이 그 예다.
exception specification은 C++98에 도입됐고 C++11에서 deprecated된 기능이다. 문법은 아래와 같다.
doubleharm(double a)throw(bad_thing); // bad_thing 에러를 throw한다.doublemarm(double)throw(); // 어떤 에러도 throw하지 않는다.
위 구문은 두 가지 역할을 한다.
프로그래머에게 해당 함수가 어떤 예외를 던질 수 있는지 명시한다. → 그냥 주석으로 하면 된다.
컴파일러에게 정말로 이 약속을 지켰는지 검사할 수 있게 한다. → 때때로 변하는 코드 때문에 사실 큰 의미가 없다.
결국 C++11에서 exception specification은 사라졌고 이를 대신할 새로운 키워드가 생겼다. 바로 noexcept다. noexcept는 throw()와 동일한 의미로 해당 함수가 던지는 exception이 없다는 의미다. 이 키워드는 문맥적인 의미뿐만이 아니라 컴파일러가 더 효율적으로 컴파일할 수 있도록 도와준다. (try 구문에서 최적화하는 듯?)
throw 구문을 jump라고 이해하라고 위에서 말했다. 하지만 이게 어떻게 가능할까? 이제까지 함수의 실행순서에 관여하는 것은 return 뿐이었다. 함수는 호출되면 파라미터를 포함해 지역변수들을 모두 Stack에 생성한다. 그 후, return이 되면 Stack에 있던 메모리를 return adress까지 모두 해제한다. 그렇다면 throw는?
throw가 되면 return과는 달리 return address가 아니라 try의 return address까지 모든 메모리를 해제한다. 이를 Unwinding the Stack이라고 한다. 그래서 Stack에 할당된 메모리들이 안전하게 해제될 수 있는 것이다.
catch 구문에 걸리지 않는 type의 예외 → 해당 exception은 unexpected exception로 branded 된다.
try를 하지 않았는데, throw를 하는 경우 → 해당 exception은 undefined exception로 branded 된다.
기본적으로 두 예외가 발생해버리면 abort()가 된다. 정확히 말하자면 abort() 함수를 바로 호출하는게 아니라 terminate() 함수를 부르고 이 함수가 기본적으로 내부에서 abort()를 호출한다. 이러한 방식을 바꾸기 위해서 다음 함수들이 <exception>헤더에 정의되어 있다.
High bar;
const High * pbar = &bar;
High * pb = const_cast<High *>(pbar);
이런 기능은 const를 잠시 떼놓아야 할 때 사용된다. const_cast는 기존 캐스팅을 통해서도 가능한데, 기존 캐스팅은 아예 type까지 바꿀 수 있다.
High * pb = (High *)(pbar);
Low * pl = (Low *)(pbar); // 다운 캐스팅과 컨스트 캐스팅을 동시에 할 수 있다.
const_cast만 보면 const 특성을 아예 지워버리는 것 같은데, 실제로는 그렇지 않다. 다음 예제를 보자.
intmain(){
int pop1 = 38383;
constint pop2 = 2000;
change(&pop1, -103); // pop1은 캐스팅된 const이기 때문에 값이 바뀐다.
change(&pop2, -103); // 이 때, pop2는 진짜 const이기 때문에 값이 바뀌지 않는다.cout << pop1 << " " << pop2 << endl;
}
voidchange(constint * ptr, int n){
int * pc;
pc = const_cast<int *>(ptr);
*pc += n;
}
즉, 변수의 const 특성 자체를 없애는 것이 아니라 임시로 const가 붙었을 때, 이를 없애주는 역할인 것이다.