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
관리 메뉴

로또

6. 컴파일러가 만들어낸 함수가 필요 없으면 확실히 이들의 사용을 금해 버리자. 본문

책/Effective C++

6. 컴파일러가 만들어낸 함수가 필요 없으면 확실히 이들의 사용을 금해 버리자.

아롱로또 2023. 11. 3. 02:46

다루는 내용

5장에서 컴파일러가 은근슬쩍 만들어 호출해 버리는 함수들에 대해 배웠다. 6장에서는 이러한 함수의 생성을 원치 않을 때, 금지해버리는 방법에 대해 다룬다.

 

객체의 복사본을 생성하고 싶지 않을 때

부동산 중개업 프로그램을 만들 때, "모든 자산은 유일하므로 객체의 사본 생성을 금지하는 경우"를 가정하자.

즉, HomeForSale Class의 복사를 금지하고 싶다.

class HomeForSale{...};

int main(){
    HomeForSale h1;
    HomeForSale h2;
    HomeForSale h3(h1); // 객체 h1을 복사해 객체 h3를 생성한다.
    ...
    return 0;
}

프로그래머가 별도로 복사 생성자나 대입 연산자를 생성하지 않아도 컴파일러는 위와 같은 코드를 보고 복사 생성자를 만들어서 호출하곤 한다.

객체의 복사본을 생성하고 싶지 않다면, 다음과 같은 방법을 시도해볼 수 있다.

 

1. 복사 생성자 및 대입 연산자를 private 멤버로 선언한다.

컴파일러가 생성하는 함수는 public 멤버로 선언된다.

 

우리가 복사 생성자 및 대입 연산자를 private하게 직접 선언하면 컴파일러는 마음대로 함수를 만들어 호출할 수 없을 것이며 private 멤버이므로 외부로부터의 호출을 차단할 수 있다.

#include <iostream>

using namespace std;

class HomeForSale{
public:
    HomeForSale(){} // 기본 생성자 선언

private:
    // 복사 생성자
    HomeForSale(const HomeForSale& hfs){
        cout << "Copy Constructor" << endl;
    }
    HomeForSale& operator=(const HomeForSale& rhs){
        cout << "Assignment Operator" << endl;
    }
};

int main(){
    HomeForSale h1;
    HomeForSale h2;

    HomeForSale h3(h1);
    HomeForSale h4 = h2;
    
    return 0;
}

private 멤버로 선언한 복사 생성자 및 대입 연산자의 호출 에러

하지만, private 멤버는 해당 클래스의 멤버 함수 내에서 사용될 수 있으므로 다음과 같이 호출될 수 있다.

#include <iostream>

using namespace std;

class HomeForSale{
public:
    HomeForSale(){} // 기본 생성자 선언
    void Test(const HomeForSale& hfs){
        cout << "Test Start" << endl;
        HomeForSale tmp(hfs); // private 복사 생성자 호출
        tmp = hfs; // private 대입 연산자 호출
        cout << "Test Success" << endl;
    }
private:
    // 복사 생성자
    HomeForSale(const HomeForSale& hfs){
        cout << "Copy Constructor" << endl;
    }
    HomeForSale& operator=(const HomeForSale& rhs){
        cout << "Assignment Operator" << endl;
    }
};

int main(){
    HomeForSale h1;
    HomeForSale h2;
    h2.Test(h1);    
    return 0;
}

 

실행 결과

2. 복사 생성자 및 대입 연선자를 선언만 하고 정의하지 않는다.

위처럼 private멤버로 선언된 복사 생성자와 대입 연산자는 해당 클래스의 멤버 함수나 friend 함수가 호출할 수 있다. 이와 같은 경우를 막기 위해 다음과 같이 함수를 선언만 하고 정의하지 않는다.

#include <iostream>

using namespace std;

class HomeForSale{
public:
    HomeForSale(){} // 기본 생성자 선언
    void Test(const HomeForSale& hfs){
        cout << "Test Start" << endl;
        HomeForSale tmp(hfs); // private 복사 생성자 호출
        tmp = hfs; // private 대입 연산자 호출
        cout << "Test Success" << endl;
    }
private:
    // 복사 생성자 정의만
    HomeForSale(const HomeForSale&);
    // 대입 연산자 정의만
    HomeForSale& operator=(const HomeForSale&);
};

int main(){
    HomeForSale h1;
    HomeForSale h2;
    h2.Test(h1);    
    return 0;
}

선언만 있고 정의되지 않은 함수를 호출할 때 링크 에러 발생

위 코드를 컴파일하면 사진처럼 링크 시점에서 에러가 발생한다.

 

복사 방지 클래스 이용하기

링크 시점 에러를 컴파일 시점 에러로 변경할 수 있다.

 

복사 생성자와 대입 연산자를 private으로 선언하되 이를 HomeForSale 클래스 자체에 넣지 않고 별도의 기본 클래스를 만들어 상속하도록 하는 것이다.

이렇게 생성한 기본 클래스는 복사 방지만을 담당한다.

#include <iostream>

using namespace std;

// 복사 방지 클래스
class UnCopyable{
// 자식에 대해 생성과 소멸 허용
protected:
    UnCopyable(){}
    ~UnCopyable(){}

private:
    UnCopyable(const UnCopyable&); // 복사 생성자 선언
    UnCopyable& operator=(const UnCopyable&); // 대입 연산자 선언
};

// 복사 방지 클래스를 상속함으로 복사 생성자 및 대입 연산자 호출이 불가하다.
class HomeForSale: private UnCopyable{
public:
    HomeForSale(){} // 기본 생성자 선언
    void Test(const HomeForSale& hfs){
        cout << "Test Start" << endl;
        const HomeForSale tmp(hfs);
        tmp = hfs;
        cout << "Test Success" << endl;
    }
};

int main(){
    HomeForSale h1;
    HomeForSale h2;
    h2.Test(h1);    
    return 0;
}

복사 방지 클래스를 상속한 클래스에서 복사 생성자나 대입 연산자를 사용할 경우 발생하는 컴파일 에러

 

Boost 라이브러리의 noncopyable 클래스는 위와 같은 복사 방지 클래스로서의 기능을 실제로 적용하고 있다고 한다.

 

이것만은 잊지 말자!

  • 컴파일러에서 자동으로 제공하는 기능을 허용하지 않으려면, 대응되는 멤버 함수를 private으로 선언한 후에 구현은 하지 않은 채로 두자. UnCopyable과 비슷한 기본 클래스를 사용하는 것도 하나의 방법이 될 수 있다.