티스토리 뷰
[Effective Modern C++] 항목 26. 보편 참조에 대한 중복적재를 피하라
pppgod 2019. 12. 1. 19:11이전 항목에서 우리는 보편 참조에 대한 중복적재의 문제점에 대해 이야기하였고 문제를 해결하기도 하였다. 다음과 같이 말이다.
template<typename T>
void logAndAdd(T&& name)
{
auto now = std:;chrono::system_clock::now();
log(now, "logAndAdd");
names.emplace(std:;forward<T>(name));
}
std::string petName("Darla");
logAndAdd(petName);
logAndAdd(std::string("Persephone"));
logAndAdd("Patty Dog");
위의 코드는 어떤 상황에서든 함수 하나로 우리가 원하대로 수행해준다. 그런데 여기서 오버로딩 함수 하나를 더 추가한다고 해보자.
void logAndAdd(int idx)
{
auto now = std::chrono::system_clock::now();
log(now, "logAndAdd");
names.emplace(nameFromIdx(idx));
}
이 함수는 통상적인 문제에 대해서는 적절하게 동작한다. 그런데 int 가 아닌 short 를 전달하게 되면 어떻게 될까?
short nameIdx;
...
logAndAdd(nameIdx);
마지막 줄에서 에러가 발생한다. 그 이유를 살펴보면 다음과 같다. 먼저 보편 참조를 받는 버전에서는 short 를 short& 로 연역할 수 있다. 반면에 int 를 받는 버전에서는 short 에서 int 로 승격해야 한다. 규칙에 따르면 정확한 부합이 승격보다 우선시되기 때문에 보편 참조를 사용하는 함수가 호출되게 된다.
보편 참조를 사용하는 곳에서는 short 를 이용하여 string 객체를 생성하려고 하는데 string 객체의 생성자로 short 는 사용될 수 없다. 그 결과 에러가 발생하는 것이다.
위와 같은 문제를 피하는 방법 중 하나는 완벽 전달 생성자를 작성하는 것이다. 예제를 조금 수정하여 Person 이라는 클래스를 도입해보자. 다음과 같이 작성할 수 있다.
class Person {
public:
template<typename T>
explicit Person(T&& n)
: name(std::forward<T>(n)) {}
explicit Person(int idx)
: name(nameFromIdx(idx)) {]
private:
std::string name;
};
위와 같은 상황에서도 int 이외의 정수 형식을 넘겨주면 보편 참조를 받는 생성자가 호출되고 컴파일에 실패한다. 그런데 이전보다 문제가 심각해졌다. 그 이유는 항목 17에서 말했듯이 자동으로 복사 생성자와 이동 생성자가 생성되기 때문이다. 다음과 같이 생성된다.
class Person {
public:
template<typename T>
explicit Person(T&& n)
: name(std::forward<T>(n)) {}
explicit Person(int idx);
Person(const Person& rhs);
Person(Person&& rhs);
...
};
위와 같은 이유 때문에 아래 코드에서 문제가 발생한다.
Person p("Nancy");
auto cloneOfP(P); // 컴파일 에러
위의 코드에서 에러가 발생하는 이유는 복사 생성자가 호출되는 것이 아닌 완벽 전달 생성자를 호출한다. string 객체를 생성하는데 Person 을 사용하는 방법이 없기 때문에 에러가 발생한다. 위와 같은 문제가 발생하는 이유는 다음과 같다. cloneofP 는 const 가 아닌 왼값으로 초기화된다. 따라서 템플릿화된 생성자를 Person 형식의 비 const 왼값을 받는 형태로 인스턴스화할 수 있다. 아래와 같이 말이다.
class Person {
public:
explicit Person(Person& n)
: name(std::forward<Person&>(n)) {}
explicit Person(int idx);
Person(const Person& rhs);
...
};
아까 에러가 발생했던 코드로 돌아가서, p 객체는 복사생성자를 호출하기 위해서는 const 를 추가해야한다. 그런데 인스턴스화된 생성자는 아무것도 추가하지 않아도 된다. 따라서 컴파일러는 복사생성자가 아닌 완벽 전달 생성자를 호출하게 된다. 만약 p 가 const 객체였다면 복사생성자를 호출했을 것이다.
하지만 위와 같은 방법으로 모든 문제가 해결되지 않는다. 템플릿화된 생성자 역시 복사 생성자와 동일한 서명으로 인스턴스화 될 수 있기 때문이다. 심지어 상속이 관여하는 클래스에서는 그 여파가 더욱 커진다.
class SpecialPerson: public person {
public:
SpecialPerson(const SpecialPerson& rhs)
: Person(rhs)
{ ... }
SpecialPeron(SpecialPerson&& rhs)
: Person(std::move(rhs))
{ ... ]
};
복사생성자와 이동생성자 모두 완벽 전달 생성자를 호출한다. 그 이유는 앞서 말한 문제점과 같이 SpecialPerson 형식의 객체가 전달되기 때문에 완벽 전달 생성자가 정확히 부합한다.
위와 같은 문제점을 해결하는 방법은 다음 항목에서 알아보도록 하자.
참고 서적
스콧 마이어스, Effective Modern C++
'C++ > Effective Modern C++' 카테고리의 다른 글
[Effective Modern C++] 항목 28. 참조 축약을 숙지하라 (0) | 2019.12.08 |
---|---|
[Effective Modern C++] 항목 27. 보편 참조에 대한 중복적재 대신 사용할 수 있는 기법들을 알아 두라 (0) | 2019.12.08 |
[Effective Modern C++] 항목 25. 오른값 참조에는 std::move를, 보편 참조에는 std::forward를 사용하라 (0) | 2019.11.30 |
[Effective Modern C++] 항목 24. 보편 참조와 오른값 참조를 구별하라 (2) | 2019.11.23 |
[Effective Modern C++] 항목 23. std::move와 std::forward를 숙지하라 (0) | 2019.11.23 |
- Total
- Today
- Yesterday
- C++11
- Forwarding
- Join
- Effective
- C
- 다이소
- 보편참조
- 발아시기
- 포인터
- 보편 참조
- const
- Perfect
- Future
- 람다
- async
- std::move
- Override
- Unreal
- forward
- MOVE
- auto
- detach
- Overloading
- thread
- CPP
- Effective Modern C++
- C++14
- Modern
- std::forward
- C++
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |