복사생성자는 객체의 복사본을 생성할 때 호출되는 생성자이다. 주로 클래스 내부에서 메모리를 동적 할당 및 해제하고 이를 멤버 포인터 변수로 관리하고 있는 경우에 사용된다.
클래스이름(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 ¶m)
& 하나로 극과 극 수준으로 성능이 달라진다.
복사 생성을 막은것을 볼 수 있다.
하지만 매개변수가 참조자가 되었을 때 한가지 단점이 있다.
33 Rain a(10);
34 TestFunc(a);
사용자가 쓰는 이 코드만 봐서는 '참조에 의한 호출 인지 아닌지 도무지 알 수가 없다는 점'이다
사용자쪽에서 '값에 의한 호출'인지 '참조에 의한 호출'인지 꼭 구별해서 알아야 하는 이유는 실인수로 기술한 변수가 함수 호출 때문에 값이 변경될 수 있기 때문이다. 그러므로 매개변수 형식이 클래스 형식이라면 무조건 상수형 참조로 선언하자.
23 void TestFunc(Rain ¶m) -----> void TestFunc(const Rain ¶m)
이렇게 바꿔줌으로 피호출자 함수에서 매개변수 인스턴스의 값을 변경했던
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 |
댓글