이를 위해서((AnotherClass*)(ptr.get()))와 같이 강제로 포인터를 얻어 캐스팅을 해줄 수 있지만 전혀 C++ 답지 못하다.
따라서 static_pointer_cast / dynamic_pointer_cast / const_pointer_cast 가 추가되었다.
이를 통해 안전하고도 편한 스마트 포인터 캐스팅이 가능해진 것이다.
vector<shared_ptr<MediaAsset>> assets;
assets.push_back(shared_ptr<Song>(new Song(L"Himesh Reshammiya", L"Tera Surroor")));
assets.push_back(shared_ptr<Song>(new Song(L"Penaz Masani", L"Tu Dil De De")));
assets.push_back(shared_ptr<Photo>(new Photo(L"2011-04-06", L"Redmond, WA", L"Soccer field at Microsoft.")));
vector<shared_ptr<MediaAsset>> photos;
copy_if(assets.begin(), assets.end(), back_inserter(photos), [] (shared_ptr<MediaAsset> p) -> bool {
// Use dynamic_pointer_cast to test whether
// element is a shared_ptr<Photo>.
shared_ptr<Photo> temp = dynamic_pointer_cast<Photo>(p);
return temp.get() != nullptr;
});
for (const auto& p : photos) {
// We know that the photos vector contains only
// shared_ptr<Photo> objects, so use static_cast.
wcout << "Photo location: " << (static_pointer_cast<Photo>(p))->location_ << endl;
}
2. 그 후 expr의 형식을 ParamType에 대해 pattern-matching방식으로 대응시켜 T의 형식을 결정한다.
template<typename T>
void f(T& param); // param은 참조형식
int x = 27; // x는 int
const int cx = x; // cx는 const int
const int& rx = x; // rx는 const int인 x에 대한 참조
/* 여러 호출에서 param과 T에 대해 연역된 형식 */
f(x); // T는 int, param의 형식은 int&
f(cx); // T는 const int, param의 형식은 const int&
f(rx); // T는 cosnt int, param의 형식은 const int&
위 f(cx), f(rx)의 param은 const값이고, 이는 그 객체가 수정되지 않을 것이라 기대한다.
즉, 해당 매개변수가 const에 대한 참조일 것이라 기대한다. (T& 매개변수를 받는 템플릿에 const객체를 전달해도 안전한 이유)
f(rx)에서 비록 rx의 형식이 참조이지만 T는 비참조로 연역된 것에 주목하면 이는 형식연역과정에서 rx의 참조성이 무시되기 때문임을 알 수 있다.
만약 f의 매개변수 형식을 T&에서 const T&로 바꾸게 된다면 상황이 달라지지만 큰 변화는 없다.
cx와 rx의 const성은 유지되며 단, param이 const에 대한 참조로 간주되므로
const가 T의 일부로 연역될 필요는 없다.
template<typename T>
void f(const T& param); // param이 cosnt에 대한 참조
int x = 27; // x는 int
const int cx = x; // cx는 const int
const int& rx = x; // rx는 const int인 x에 대한 참조
/* 여러 호출에서 param과 T에 대해 연역된 형식 */
f(x); // T는 int, param의 형식은 const int&
f(cx); // T는 int, param의 형식은 const int&
f(rx); // T는 int, param의 형식은 const int&
이전처럼 rx의 참조성은 형식연역과정에서 무시된다.
이는 아래처럼 param을 참조가 아닌 포인터, const를 가리키는 포인터라도 형식연역은 본직적으로 같은 방식을 취한다.
template<typename T>
void f(T* param); // param이 cosnt에 대한 참조
int x = 27; // x는 int
const int *px = x; // px는 const int로 x를 가리키는 포인터
/* 여러 호출에서 param과 T에 대해 연역된 형식 */
f(&x); // T는 int, param의 형식은 int*
f(px); // T는 const int, param의 형식은 const int*
여기까지는 너무 쉬워서 하품이 날 지경이라고 한다.(물론 저자가 그런거지 난 아니다. 어려워 죽을거 같다.)
Case 2. ParamType이 보편참조임.
템플릿이 보편참조매개변수를 받는 경우, 상황이 불투명해진다.
그런 매개변수 선언은 rvalue 참조와 같은 모습이다. (보편참조의 선언형식은 T&&이다.)
l-value가 전달되면 r-value참조와는 다른 방식으로 행동한다.
- 만약 expr이 l-value면, T와 ParamType 둘 다 l-value 참조로 연역된다. (비정상적 상황)
i) 템플릿 형식 연역게서 T가 참조형식으로 연역되는 경우, 이것이 유일
ii) ParamType의 선언구문은 r-value 참조와 같지만 연역된 형식은 l-value참조이다.
- 만약 expr이 r-value면, '정상적인' 규칙들이 적용된다. (Case 1의 규칙들.)
template<typename T>
void f(T&& param); // 이번에는 param이 보편 참조
int x = 27; // x는 int
const int cx = x; // cx는 const int
const int& rx = x; // rx는 const int인 x에 대한 참조
f(x); // x는 l-value, 따라서 T는 int&, param 형식도 int&
f(cx); // cx는 l-value, 따라서 T는 const int&, param 형식도 const int&
f(rx); // rx는 l-value, T는 const int&, param 형식도 const int&
f(27); // 27은 r-value, 따라서 T는 int, param 형식은 int&&
나중에 item 24에서 설명될 것이고, 지금은 보편참조매개변수에 관한 형식연역 규칙들이
l-value참조나 r-value참조 매개변수들에 대한 규칙들과는 다르다는 점만 기억하면 된다.
특히 보편참조가 관여하는 경우, l-value와 r-value에 대해 서로 다른 연역규칙이 적용된다. (보편참조가 아니면 발생X)
Case 3. ParamType이 포인터도 아니고 참조도 아님
ParamType이 포인터도 참조도 아닌 경우, 인수가 함수에 값으로 전달되는 pass-by-value인 상황이다.
param이 새로운 객체라는 사실 때문에 expr에서 T가 연역되는 과정에서 다음과 같은 규칙들이 적용된다.
1. 이전처럼, 만약 expr의 형식이 참조라면, 참조부분은 무시된다.
2. expr의 참조성을 무시한 후 만약 expr이 const라 해도 그 const 역시 무시한다.
만약 volatile객체라도 그것도 무시한다. (volatile객체는 흔하지 않기에 (장치구동기 구현시 사용) item 40 참고)
다음은 이 규칙들이 적용되는 예이다.
template<typename T>
void f(T&& param); // 이번에는 param이 보편 참조
int x = 27; // x는 int
const int cx = x; // cx는 const int
const int& rx = x; // rx는 const int인 x에 대한 참조
f(x); // T와 param의 형식 둘 다 int
f(cx); // 여전히 T와 param의 형식 둘 다 int
f(rx); // 여전히 T와 param의 형식 둘 다 int
이때, cx와 rx는 const값이지만, param은 const가 아님을 주목하자.
param은 cx, rx의 복사본이므로 (param은 cx, rx와 달리 완전히 독립적인 객체이므로) 당연한 결과이다.
cx, rx가 수정될 수 없다는 점(const)은 param의 수정가능 여부와는 무관하기 때문이다.
param의 형식을 연역하는 과정에서 expr의 const성이 무시되는 이유가 바로 이것이다.
expr을 수정할 수 없다해서 그 복사본까지 수정할 수 없는 것은 아니기 때문이다.
여기서 명심할 점은, const가 값 전달 매개변수에 대해서만 무시된다는 점이다.
즉, const에 대한 참조나 포인터 매개변수의 경우, 형식연역과정에서 expr의 const성이 보존된다.
그러나 expr이 cosnt객체를 가리키는 const포인터이고 param에 값으로 전달되는 경우는 어떨까?
template<typename T>
void f(T param); // 인수는 param에 여전히 값으로 전달됨
const char* const ptr = "V2LLAIN"; // ptr은 const 객체를 가리키는 const 포인터
f(ptr); // const char* const 형식의 인수를 전달
포인터 선언 오른쪽 const로 ptr 자체가 const가 된다. (const char* const ptr)
즉, ptr을 다른 장소를 가리키도록 변경불가능! (nullptr도 배정할 수 없다.)
포인터 선언 왼쪽 const가 가리키는 것은 문자열이 const인 것을 의미한다. (const char* const ptr)
ptr을 f에 전달하면 그 포인터를 구성하는 bit들이 param에 복사되며 포인터자체(ptr)는 값으로 전달된다.
형식연역과정에서 ptr의 const성은 무시되며 따라서 param에 연역되는 형식은 const char*이다.
즉, 형식연역과정에서 ptr이 가리키는 것의 const성은 보존되나
ptr 자체의 const성은 ptr을 복사해 새 포인터 param을 생성하는 도중 사라진다!
※ 템플릿 연역과정에서 배열이나 함수이름에 해당하는 인수는 포인터로 붕괴(decay)한다.
(단, 그런 인수가 참조를 초기화하는데 사용되면, 그 경우에는 포인터로 붕괴하지 않는다.)
- 템플릿형식 연역 중 참조형식의 인수들은 비참조로 취급된다. 즉, 참조성이 무시된다.
- 보편참조 매개변수에 대한 형식 연역과정에서 l-value들은 특별하게 취급된다.
- 값 전달방식의 매개변수에 대한 형식연역과정에서 const, volatile 인수는 비 const, 비 volatile 인수로 취급된다.
- 템플릿형식 연역과정에서 배열이나 함수이름에 해당하는 인수는 포인터로 붕괴한다.
단, 그런 인수가 참조를 초기화하는데 쓰이는 경우, 포인터로 붕괴하지 않는다.