class Widget {
public:
using DataType = std::vector;
...
DataType& data() & // 왼값 Widget에 대해서는 왼값을 반환
{ return values; }
DataType&& data() && // 오른값 Widget에 대해서는 오른값을 반환
{ return std::move(values); }
...
private:
DataType values;
};
data 중복적재들의 반환 형식이 서로 다르다는 점에 주목하기 바랍니다. 왼값 참조 중복적재는 왼값 참조(즉, 하나의 왼값)를 돌려주고 오른값 참조 중복적재는 오른값 참조를 돌려줍니다(함수 반환 형식으로서의 오른값 참조는 하나의 오른값 입니다). 이제 클라이언트 코드는 우리가 원했던 방식으로 작동합니다.
auto vals1 = w.data() // Widget::data의 왼값 중복적재를 호출; vals1은 복사 생성됨
auto vals2 = makeWidget().data(); // Widget::data의 오른값 중복적재를 호출; vals 2는 이동 생성됨
확실히 멋진 일이긴 하지만, 행복감은 그만 누리고 이 항목의 진정한 요점으로 넘어갑시다. 요점은, 기반 클래스의 가상 함수를 재정의하려는 의도를 가진 멤버 함수를 파생 클래스 안에서 선언할 때에는 그 함수를 반드시 override로 선언하라는 것입니다.
그런데 한 멤버 함수에 참조 한정사가 붙어 있으면 그 함수의 모든 중복적재에도 참조 한정사를 지정해야 합니다. 이는 참조 한정되지 않은 중복적재가 왼값 객체와 오른값 객체 모두에 대해 호출될 수 있기 때문입니다. 그런 중복적재는 참조 한정된 중복적재들과 경쟁하며, 따라서 그 함수에 대한 모든 호출이 중의적인 호출로 변합니다.
*** Key Point ***
- 재정의 함수는 override로 선언할 것.
- 멤버 함수 참조 한정사를 이용하면 멤버 함수가 호출되는 객체(*this)의 왼값 버전과 오른값 버전을 다른 방식으로 처리할 수 있습니다.
Chapter (13) iterator보다 const_iterator를 선호할 것
const_iterator는 const를 가리키는 포인터의 STL 버전입니다. const_iterator는 수정하면 안 되는 값들을 가리킵니다. 가능한 한 항상 const를 사용하라는 표준 관행은 반복자에도 적용됩니다. 즉, 반복자가 가리키는 것을 수정할 필요가 없을 때에는 항상 const_iterator를 사용하는 것이 바람직합니다.
이 점은 C++11에서는 물론 C++98에서도 참이었지만, C++98에서는 const_iterator로 이 관행을 지키는 것이 그리 즐겁지 않았습니다. 그런 반복자를 생성하기가 쉽지 않았으며, 생성했다고 해도 그 활용에 제약이 있었습니다. 예를 들어 std::vector에서 1983이라는 값(1983은 프로그래밍 언어의 이름으로 "C with Classes" 대신 C++을 사용하기로 한 해입니다)이 처음 나오는 지점을 찾고 그곳에 1998이라는 값(1998은 첫 번째 ISO C++ 표준이 채택된 해입니다)을 삽입한다고 합시다. 벡터에 1983이 하나도 없으면 1998을 벡터에 끝에 삽입해야 합니다. C++98에서 iterator를 이용해서 이를 수행하는 것은 쉬운 일이었습니다.
std::vector values;
...
std::vector::iterator it =
std::find(values.begin(), values.end(), 1983);
values.insert(it, 1998);
그러나 이 코드는 iterator가 가리키는 것을 전혀 수정하지 않으므로, iterator가 최선의 선택은 아닙니다. iterator 대신 const_iterator를 사용하도록 코드를 고치는 것은 간단한 일이어야 마땅하지만, C++98에서는 전혀 그렇지 않았습니다. 다음은 개념적으로는 건전하지만 실제로는 정확하지 않은 접근방식입니다.
typedef std::vector::iterator IterT; // typedef들
typedef std::vector::const_iterator CostIterT;
std::vector values;
...
ConstIterT ci =
std::find(static_cast(values.begin()), // 캐스팅
static_cast(values.end()), // 캐스팅
1983);
values.insert(static_cast(ci), 1998); // 컴파일이 안 될 수 있음;
물론 typedef가 꼭 필요한 것은 아니나, 이들 덕분에 코드의 캐스팅을 좀 더 쉽게 작성할 수 있습니다. (별칭 선언을 선호하라는 Chapter(9)의 조언을 따르지 않고 typedef를 사용한 이유는, 별칭 선언이 C++11에서 새로 생긴 기능이기 때문입니다. 이 예는 C++98의 코드를 보여줍니다)
'컴퓨터과학' 카테고리의 다른 글
[Effective C++] (13-3) iterator보다 const_iterator를 선호할 것 (0) | 2020.05.31 |
---|---|
[Effective C++] (13-2) iterator보다 const_iterator를 선호할 것 (0) | 2020.05.31 |
[Effective C++] (12-3) 재정의 함수들을 override로 선언할 것 (0) | 2020.05.31 |
[Effective C++] (12-2) 재정의 함수들을 override로 선언할 것 (0) | 2020.05.29 |
[Effective C++] (12-1) 재정의 함수들을 override로 선언할 것 (0) | 2020.05.29 |