티스토리 뷰

 이전 항목에서 우리는 보편 참조에 대한 중복적재의 문제점에 대해 이야기하였고 문제를 해결하기도 하였다. 다음과 같이 말이다.

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 를 추가해야한다. 그런데 인스턴스화된 생성자는 아무것도 추가하지 않아도 된다. 따라서 컴파일러는 복사생성자가 아닌 완벽 전달 생성자를 호출하게 된다. 만약 pconst 객체였다면 복사생성자를 호출했을 것이다. 

 하지만 위와 같은 방법으로 모든 문제가 해결되지 않는다. 템플릿화된 생성자 역시 복사 생성자와 동일한 서명으로 인스턴스화 될 수 있기 때문이다. 심지어 상속이 관여하는 클래스에서는 그 여파가 더욱 커진다. 

class SpecialPerson: public person {
public:
    SpecialPerson(const SpecialPerson& rhs)
    : Person(rhs)
    { ... }
    
    SpecialPeron(SpecialPerson&& rhs)
    : Person(std::move(rhs))
    { ... ]
};

 복사생성자와 이동생성자 모두 완벽 전달 생성자를 호출한다. 그 이유는 앞서 말한 문제점과 같이 SpecialPerson 형식의 객체가 전달되기 때문에 완벽 전달 생성자가 정확히 부합한다.

위와 같은 문제점을 해결하는 방법은 다음 항목에서 알아보도록 하자.

 

참고 서적

스콧 마이어스, Effective Modern C++
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/03   »
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
글 보관함