#12. [모던 C++] 멤버 함수 참조 지정자(C++11)
- (C++11~) 멤버 함수 참조 지정자가 추가되어 멤버 함수에
&
,&&
로 좌측값에서 호출될때와 우측값에서 호출될 때를 구분하여 함수 오버로딩을 할 수 있습니다.
개요
멤버 함수 참조 지정자가 추가되어 멤버 함수에 &
, &&
로 좌측값에서 호출될때와 우측값에서 호출될 때를 구분하여 함수 오버로딩을 할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
class T {
public:
int Func_11() & {return 1;} // #1. 좌측값에서 호출
int Func_11() && {return 2;} // #2. 우측값에서 호출
};
T t;
EXPECT_TRUE(t.Func_11() == 1); // 좌측값이므로 #1 호출
EXPECT_TRUE(std::move(t).Func_11() == 2); // move는 우측값이므로 #2 호출
EXPECT_TRUE(T{}.Func_11() == 2); // T{} 는 임시 개체(우측값)이므로 #2 호출
이동 연산을 지원하는 래퍼
이동 연산을 지원하기 위해 이동 생성자를 구현한 Big_11
개체가 있다고 합시다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Big_11 {
public:
Big_11() {}
~Big_11() {}
// 복사 생성자
Big_11(const Big_11& other) {
std::cout << "Big_11 : Copy Constructor" << std::endl;
}
// 이동 생성자
Big_11(Big_11&& other) noexcept {
std::cout << "Big_11 : Move Constructor" << std::endl;
}
};
이를 관리하는 래퍼는 다음과 같이 만들 수 있습니다.
1
2
3
4
5
6
7
8
class Wrapper_11 {
Big_11 m_Data;
public:
Wrapper_11() : m_Data{} {}
const Big_11& GetData() const {
return m_Data;
}
};
GetData()
를 호출하는 곳에서 쓸데없이 복사 생성하지 않도록 const Big_11&
를 리턴했습니다.
1
2
Wrapper_11 wrapper;
const Big_11& big{wrapper.GetData()}; // const Big_11&을 리턴하므로 참조만 합니다.
이를 복사해서 사용하고 싶다면 다음과 같이 하면 됩니다.
1
2
Wrapper_11 wrapper;
Big_11 big{wrapper.GetData()}; // const Big_11& 를 리턴한 것을 Big_11로 받으므로 복사 생성 합니다.
만약 wrapper
가 이제 버려질 값이라면, 복사 부하를 줄이기 위해 이동 생성자를 호출하는게 좋습니다. 하지만 다음과 같이 wrapper
개체를 우측값으로 만들어 봤자, GetData()
는 const Big_11&
인 좌측값 참조를 리턴하므로, Big_11
의 이동 생성자는 호출되지 않습니다.
1
2
3
4
5
6
// std::move(wrapper)로 우측값으로 만들어 봤자, std::move(a).GetData()는 const Big_11&(좌측값 참조)를 리턴
Wrapper_11 wrapper;
Big_11 big{std::move(wrapper).GetData()}; // 복사 생성자를 호출합니다.
// Wrapper_11()은 임시 개체인 우측값 이지만, 여전히 GetData()는 const Big_11&(좌측값 참조)를 리턴
Big_11 big{Wrapper_11{}.GetData()}; // 복사 생성자를 호출합니다.
그러면 다음은 될까요? 리턴값을 move()했지만, const Big_11&
를 const Big_11&&
로 바꾸기 때문에(상수 개체의 move() 참고), 이동 생성자와 인자 타입이 달라 (이동 생성자는 Big_11(Big_11&& other)
입니다. const가 다르죠.) 그냥 복사 생성자를 호출합니다.
1
2
3
4
5
6
// Wrapper_11().GetData()는 const Big_11&를 리턴하고, move()는 const Big_11&& 을 리턴. 이동 생성자와 인자 타입과 다르므로, 그냥 복사 생성자 호출
Big_11 big{
std::move(
Wrapper_11{}.GetData()
)
};
다음처럼 const_cast로 상수성을 버려야 이동 생성을 할 수 있습니다.
1
2
3
4
5
6
7
8
// Big_11의 이동 생성자 호출
Big_11 big{ // 3. 이동 생성자 호출
std::move( // 2. Big&&로 변환하여
const_cast<Big_11&>( // 1. 상수성을 떼고
Wrapper_11{}.GetData()
)
)
};
되기는 합니다만, 참 실수하기 쉽고, 타이핑도 번거롭고 const_cast는 상수성 계약을 바꾸는 거라 싫습니다.
캡슐화에 언급되었듯, 잘못 사용하기엔 어렵게, 바르게 사용하기엔 쉽게 구현할 필요가 있는데요, 멤버 함수 참조 지정자가 이럴때 딱 좋습니다.
1
2
3
4
5
6
7
8
9
10
11
12
class Wrapper_11 {
Big_11 m_Data;
public:
Wrapper_11() : m_Data() {}
const Big_11& GetData() const & { // #1. Wrapper_11가 좌측값일때 호출됩니다.
return m_Data;
}
Big_11&& GetData() && {
return std::move(m_Data); // #2. Wrapper_11가 우측값일때 호출됩니다.
}
};
기존 GetData()
함수에 &
을 붙였고, GetData() &&
함수 오버로딩 버전을 추가하였습니다. 이제 Wrapper_11
이 임시 개체(우측값) 일때는 알아서 GetData() &&
버전을 호출하므로, 사용하기 훨씬 수월합니다.
1
2
3
4
5
6
7
8
9
10
// wrapper은 좌측값. #1이 호출되어 const Big_11&을 리턴하고, Big_11의 복사 생성자 호출
Wrapper_11 wrapper;
Big_11 big = wrapper.GetData();
// wrapper은 좌측값. std::move(wrapper)는 우측값. #2가 호출되어 Big_11&&을 리턴하고, Big_11의 이동 생성자 호출
Wrapper_11 wrapper;
Big_11 big = std::move(wrapper).GetData();
// Wrapper_11{}은 임시 개체인 우측값. #2가 호출되어 Big_11&&을 리턴하고, Big_11의 이동 생성자 호출
Big_11 big = Wrapper_11().GetData();
댓글남기기