티스토리 뷰
[Effective Modern C++] 항목 41. 이동이 저렴하고 항상 복사되는 복사 가능 매개변수에 대해서는 값 전달을 고려하라
pppgod 2020. 3. 3. 22:45매개변수를 받는 방법에 대해서 우리는 왼값, 오른값, 보편 참조를 이용해서 전달하는 방법을 배웠다. 특히 왼값과 오른값 모두 구현하는 경우에는 보편 참조를 이용하여 구현하였다. 그런데 보편 참조를 이용하게 되면 우리가 원하지 않는 타입에 대해서도 인스턴스화되고, 보편 참조로 전달할 수 없는 경우에도 난감한 상황이 생기게 된다. 그런데 우리가 앞서 보았던 방법 외에도 가장 기초적인 값으로 전달하는 방법이 있다. 다음의 코드를 보자.
class Widget {
public:
void addName(std::string newName)
{ names.push_back(std::move(newName)); }
...
};
위 코드는 어떠한 문제도 발생하지 않는다. 하지만 이전부터 값 전달의 성능은 좋지 않다고 알려져왔다. 실제로 C++98 에서는 비용이 컸다. 그런데 C++11 로 오면서 왼값일 때에만 복사 생성되고, 오른값일 때에는 이동 생성된다.
성능 비교
매개변수 전달 방법을 3가지로 나누어 성능을 비교해보자.
// 오버로딩
class Widget {
public:
void addName(const std::string& newName)
{ names.push_back(newName); }
void addName(std::string&& newName)
{ names.push_back(std::move(newName); }
...
private:
std::vector<std::string> names;
};
// 보편 참조
class Widget {
public:
template<typename T>
void addName(T&& newName)
{ names.push_back(std::forward<T>(newName)); }
...
};
// 값 전달
class Widget {
public:
void addName(std::string newName)
{ names.push_back(std::move(newName)); }
...
};
중복적재(오버로딩)
왼값과 오른값 모두 참조로 전달되므로 인수로 전달되는 비용은 없다. 함수 내에서 왼값의 경우에는 복사 1회가 수행되고 오른값의 경우에는 이동 1회 수행된다.
보편 참조
오버로딩과 연산 시점과 비용이 똑같다. 함수로 전달하는 비용이 없고 본문에서 복사와 이동이 일어난다. 만일 std::string 이 아닌 타입의 경우에는 std::string 의 복사와 이동이 0회 이상 일어날 수 있다. 그러나 여기서는 std::string 의 경우에만 생각하자.
값 전달
앞서 설명한대로 왼값과 오른값으로 나누어 생각해야한다. 왼값의 경우에는 인수로 전달하는데 복사 1회가 일어나고, 오른값의 경우에는 이동 1회가 발생한다. 함수 본문에서는 두 가지 경우 모두 이동 1회가 발생한다.
성능의 비교로 알아보았듯이 값 전달이 장점만 있는 것은 아니다. 성능 뿐만 아니라 이동 전용 형식의 경우에는 우리가 값 전달을 고려할 필요가 없다. 오른값으로 매개변수를 받도록 만들었다면 인수를 전달하는데 드는 비용이 없기 때문이다. 또한 만일 addName 함수 내에 조건문에 따라 삽입을 하지 않는다면 쓸데없는 복사나 이동 연산을 하게 되는 것이다.
배정문
'=' 연산을 사용하는 경우를 배정문이라고 한다. 이 경우에는 좀 더 복잡해진다. 코드를 보자.
class Password {
public:
explicit Password(std::string pwd)
: text(std::move(pwd)) {}
void changeTo(std::string newPwd)
{ text = std::move(newPwd): }
...
private:
std::string text;
};
위 코드는 패스워드를 담고있는 객체이다. 생성자에서는 우리가 앞서 본 그대로이다. 배정문에서는 과연 어떻게 될까? 인수로 전달되는 것은 왼값이나 오른값에 따라 다른 것은 분명하다. 문제는 배정문을 수행할 때 기존에 저장되어 있던 메모리를 해제하고 새로운 객체를 이동한다. 또한 만일 기존에 저장된 메모리가 새로 배정되는 메모리보다 크다면 굳이 메모리를 해제할 필요가 없다. 만약 우리가 값 전달이 아닌 중복적재 방식을 이용했다면 할당과 해제를 생략할 수 있다.
class Password {
public:
...
void changeTo(const std::string& newPwd)
{
text = newPwd;
}
...
private:
std::string text;
};
이 이외에도 값 전달은 slice off 문제가 발생할 수 있다. 다형성과 관련된 문제로 포인터 타입의 경우에는 이런 문제가 발생하지 않는다. 값 전달을 사용하기에는 위험한 점이 너무나도 많기 때문에 주의해서 사용해야한다.
참고 서적
스콧 마이어스, Effective Modern C++
'C++ > Effective Modern C++' 카테고리의 다른 글
[Effective Modern C++] 항목 42. 삽입 대신 생성 삽입을 고려하라 (0) | 2020.03.05 |
---|---|
[Effective Modern C++] 항목 40. 동시성에는 std::atomic을 사용하고, volatile은 특별한 메모리에 사용하라 (0) | 2020.02.25 |
[Effective Modern C++] 항목 39. 단발성 사건 통신에는 void 미래 객체를 고려하라 (0) | 2020.02.24 |
[Effective Modern C++] 항목 37. std::thread들을 모든 경로에서 합류 불가능하게 만들어라 (0) | 2020.02.09 |
[Effective Modern C++] 항목 36. 비동기성이 필수일 때에는 std::laynch::async를 지정하라 (0) | 2020.02.02 |
- Total
- Today
- Yesterday
- auto
- const
- 발아시기
- async
- C
- Forwarding
- Future
- Join
- 포인터
- Override
- 보편참조
- Effective Modern C++
- Overloading
- C++11
- C++14
- 보편 참조
- Effective
- forward
- thread
- 람다
- Modern
- std::move
- Unreal
- 다이소
- std::forward
- C++
- Perfect
- CPP
- detach
- MOVE
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |