본문 바로가기
Develop/C++

[C ++] 복사 생성자

by J-rain 2023. 2. 4.

복사생성자는 객체의 복사본을 생성할 때 호출되는 생성자이다. 주로 클래스 내부에서 메모리를 동적 할당 및 해제하고 이를 멤버 포인터 변수로 관리하고 있는 경우에 사용된다.

클래스이름(const 클래스이름 &rhs); 
#include <iostream>
using namespace std;

class Rain
{
public:
    Rain()
    {
        cout << "Rain()" << endl;
    }
    // 복사 생성자 선언 및 정의
    Rain(const Rain &rhs) // : Data(rhs.Data)
    {
        this->Data = rhs.Data;  // 위의 주석처럼 초기화 목록을 이용해도 된다.
        cout << "Rain(const Rain &rhs)" << endl;
    }

    int GetData(void) const { return Data; }
    void SetData(int Param) { Data = Param; }

private:
    int Data = 0;
};

int main(void)
{
    // 디폴트 생성자가 호출되는 경우
    Rain a;
    a.SetData(10);

    // 복사 생성자가 호출되는 경우
    Rain b(a);     //  a에 대한 참조는 12번행의 rhs
    cout << b.GetData() << endl;

    return 0;
}

실행결과

32번행의 Rain b(a); 코드에 의해 복사 생성자가 호출되었다. 여기서 복사의 원본은 a이다. 따라서 b는 a를 원본으로 생성된다.


클래스가 매개변수로 사용되는 경우

 

먼저 코드를 살펴보자

#include <iostream>
using namespace std;

class Rain
{
public:
    Rain(int Param) : Data(Param)
    {
        cout << "Rain(int)" << endl;
    }
    Rain(const Rain &rhs) : Data(rhs.Data)
    {
        cout << "Rain(const Rain &rhs)" << endl;
    }
    int GetData() const { return Data; }  // 읽기전용인 상수형 메서드
    void SetData(int Param) { Data = Param; } // 멤버 변수에 쓰기를 시도하는 메서드

private:
    int Data = 0;
};

// 매개변수가 Rain 클래스 형식이므로 복사 생성자가 호출!!
void TestFunc(Rain param)
{
    cout << "TestFunc()" << endl;
    
    // 피호출자 함수에서 매개변수 인스턴스의 값을 변경한다.
    param.SetData(20);
}
int main(void)
{
    cout << "시작" << endl;
    Rain a(10);
    TestFunc(a);
    
    // 함수 호출 후 a의 값을 출력
    cout << "a: " << a.GetData() << endl;
    cout << "끝" << endl;

    return 0;
}

실행결과

 

아마 예상과는 다르게 결과가 나왔을 가능성이 있다. 23번행의 void TestFunc(Rain param) 은 문법적으로는 아무 문제가 없다. 하지만 이 함수를 호출하기 위해 main()함수에 TestFunc(a); 라는 코드가 등장했다. 쓸데없이 Rain 객체가 두개라는 문제가 생겼다. 게다가 매개변수인 param은 함수가 호출될 때 a를 원본으로 두고 복사본도 생성된다.

따라서 TestFunc() 함수를 호출하는 일 자체가 프로그램에 부담이고 성능도 저하된다. 즉, 한 객체로 할 수 있는 일은 반드시 하나로 끝내는 것이 좋다.

해결책은 delete 예약어로 복사 생성자를 아예 삭제함으로써 복사 생성을 막는방법과 '참조자'를 이용하여 방지하는 방법이있다. 후자를 택하는것이 더 깔끔하고 한 글자만 쓰면 되기때문에 훨씬 쉽다.
23번행의 void TestFunc(Rain param)  ---->   void TestFunc(Rain &param)
& 하나로 극과 극 수준으로 성능이 달라진다.
복사 생성을 막은것을 볼 수 있다.

하지만 매개변수가 참조자가 되었을 때 한가지 단점이 있다.

33 Rain a(10);
34 TestFunc(a);
사용자가 쓰는 이 코드만 봐서는 '참조에 의한 호출 인지 아닌지 도무지 알 수가 없다는 점'이다

사용자쪽에서 '값에 의한 호출'인지 '참조에 의한 호출'인지 꼭 구별해서 알아야 하는 이유는 실인수로 기술한 변수가 함수 호출 때문에 값이 변경될 수 있기 때문이다. 그러므로 매개변수 형식이 클래스 형식이라면 무조건 상수형 참조로 선언하자.

23 void TestFunc(Rain &param)  ----->  void TestFunc(const Rain &param)
이렇게 바꿔줌으로 피호출자 함수에서 매개변수 인스턴스의 값을 변경했던 
28 param.SetData(20); 줄은 컴파일 오류가 날 것이다.

 

'Develop > C++' 카테고리의 다른 글

[C ++] r-value 참조  (0) 2023.02.20
[C ++] 변환 생성자  (0) 2023.02.04
[C ++] 상수형 메소드  (0) 2023.02.03
[C ++] 생성자와 소멸자  (0) 2023.02.03
[C ++] 접근 제어 지시자  (0) 2023.02.03

댓글