Notice
Recent Posts
Recent Comments
Link
«   2026/05   »
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
Archives
Today
Total
관리 메뉴

로또

5. C++가 은근슬쩍 만들어 호출해 버리는 함수들에 촉각을 세우자. 본문

책/Effective C++

5. C++가 은근슬쩍 만들어 호출해 버리는 함수들에 촉각을 세우자.

아롱로또 2023. 11. 2. 02:03

다루는 내용

클래스의 생성자, 복사 생성자, 복사 대입 연산자, 소멸자가 선언되어 있지 않고, 컴파일러에 의해 필요하다고 판단되면 컴파일러가 대신 생성한다. 

 

컴파일러가 만드는 함수들

class Empty{}

int main(){
    Empty e1; // 컴파일러가 기본 생성자와 소멸자를 필요하다고 판단하는 코드.
    Empty e2(e1); // 컴파일러가 복사 생성자가 필요하다고 판단하는 코드.
    e2 = e1; // 컴파일러가 복사 대입 연산자가 필요하다고 판단하는 코드.
    
    return 0;
}

 

컴파일러는 위 코드에 대해 다음과 같이 여러 함수를 만들고 호출한다.

class Empty{
public:
    Empty(){...} // 컴파일러가 생성한 기본 생성자
    Empty(const Empty& rhs){...} // 컴파일러가 생성한 복사 생성자
    ~Empty(){...} // 컴파일러가 생성한 소멸자
    Empty& operator=(const Empty& rhs){...} // 컴파일러가 생성한 복사 대입 연산자
}

int main(){
    Empty e1; // 컴파일러가 기본 생성자와 소멸자를 필요하다고 판단하는 코드.
    Empty e2(e1); // 컴파일러가 복사 생성자가 필요하다고 판단하는 코드.
    e2 = e1; // 컴파일러가 복사 대입 연산자가 필요하다고 판단하는 코드.
    
    return 0;
}

 

문제점

template<typename T>
class NamedObject{
public:
    NamedObject(const char* name, const T& value);
    NamedObject(const string name, const T& value);
private:
    string name_value;
    T object_value;
};

위 NamedObject 클래스 내에는 이미 생성자가 선언되어 있으므로 컴파일러는 기본 생성자를 생성하지 않는다.

NamedObject<int> no1("Smallest Prime Number", 2);
NamedObject<int> no2(no1);

 

하지만 복사 생성자나 복사 대입 연산자는 선언되어있지 않기 때문에, 위 코드처럼 컴파일러가 복사 생성자가 필요하다고 판단되면 만들어질 수 있다.

 

이 때, 복사 생성자에서 쓰일 string이나 int는 자체적으로 복사 생성자를 갖고 있으므로 no2에서는 단순히 no1의 각 비트를 그대로 복사해온다.

 

이제, 다음과 같은 경우를 생각해보자.

template<typename T>
class NamedObject{
public:
    NamedObject(const string name, const T& value);
    
private:
    string& name_value; // 참조자로 변경됨.
    const T object_value; // const로 변경됨.
};

int main(){
    string old_dog("ddochi");
    string new_dog("lotto");
    
    NamedObject<int> od(old_dog, 1);
    NamedObject<int> nd(new_dog, 12);
    
    od = nd; // 복사 대입 연산자 호출
    
    return 0;
}

NamedObject class의 멤버 변수가 각각 참조자와 상수로 변경되었다.

참조자와 상수에 대해서는 초기화를 제외하고 대입 연산을 허용하지 않으므로 복사 대입 연산자를 생성할 수 없다.

때문에 위와 같은 경우에는 컴파일 에러가 발생한다.

 

이러한 문제를 해결하기 위해서 참조자나 상수를 멤버 변수로 갖고 있는 클래스에 대해서는 직접 복사 대입 연산자를 정의해주어야 할 것이다.

 

이것만은 잊지 말자!

  • 컴파일러는 경우에 따라 클래스에 대해 기본 생성자, 복사 생성자, 복사 대입 연산자, 소멸자를 암시적으로 만들어놓을 수 있다.

 

복사 대입 연산자를 private로 선언한 기본 클래스로부터 파생된 클래스의 경우, 해당 클래스는 암시적 복사 대입 연산자를 가질 수 없다. 파생 클래스 쪽에서 복사 대입 연산자는 호출할 권한이 없는 멤버 함수이므로 컴파일러가 거부하기 때문이다.

이러한 성질을 이용해 위와 같은 문제 상황을 방지할 수 있다.

 

자세한 내용은 "6. 컴파일러가 만들어낸 함수가 필요 없으면 확실히 이들의 사용을 금해버리자." 로 이어진다.