복사생성자
복사생성자함수는 복사 생성 시에만 실행되는 특별한 생성자입니다.
클래스에서 오직 1개만 존재할 수 있고, 매개변수도 오직 하나로 자기 클래스에 대한 참조입니다.
선언형식은 다음과 같습니다.
class ClassName {
ClassName(const ClassName& c);
};
const는 붙여도되고 안붙여도되지만 참조를 통한 전달의 유일한 단점이 원본이 바뀔 수 있다는 점임을 고려했을 때 const를 붙이게 되면 원본이 바뀌게 되는 상황을 방지할 수 있어 붙이는 것이 좋습니다. &를 붙여 참조를 통해 전달하는 이유는 값 전달 방식(call by value)로 매개변수를 전달함에따라 불필요한 복사생성이 생기기 때문에 효율성 증대를 위해 사용한 것입니다.
복사생성자가 필요한 이유
#include<iostream>
using namespace std;
class Person {
public:
Person() { id = 0; name = new string; }
~Person() { delete name; }
void setID(int id) { this->id = id; }
void setName(string name) { *this->name = name; }
void show() { cout << *name << " " << id; }
private:
int id;
string* name;
};
int main() {
Person A, B;
A.setName("sangtae");
A.setID(23);
A.show();
B = A; //복사생성자호출
B.show();
}
이 코드를 실행하면 컴파일 에러가 발생합니다. B=A라는 문장에서 복사생성을 하였지만 Person 클래스에 복사생성자가 없어 컴파일러가 제공하는 기본복사생성자를 활용해 얕은 복사만이 이루어지기 때문입니다. 얕은 복사만으로 해결되는 경우도 있지만 다음 코드같이 string *name을 활용해 동적할당을 한 후에 복사생성이 이루어지면 심각한 오류이기 때문에 따로 복사생성자를 만들어줘야합니다. 왜 심각한 오류인 지 설명해드리겠습니다.
왼쪽이 A객체이고 오른쪽이 B객체입니다. A객체의 멤버변수에 자유기억공간(heap)을 가리키는 string포인터가 존재합니다. 그런데 기본복사생성자는 얕은 복사만을 하므로 B객체의 string 포인터를 다른 자유기억공간에 할당하는 것이 아닌 A와 똑같은 공간을 가리키도록 생성합니다. 프로그램이 종료되고, 소멸자가 실행되면서 A가 가리키는 자유기억공간이 사라지면 B의 포인터변수는 실종된 포인터가 됩니다. 그리고 B객체가 소멸될 때 B의 포인터변수는 이미 반환한 메모리를 다시 반환하게 되므로, 위와 같은 오류가 뜨는 것입니다.
복사생성자 만드는 법
얕은 복사에서 문제가 된 것이 자유기억공간이므로 복사생성자를 만들어 B객체의 string 포인트가 다른 자유기억공간을 할당 받도록하고, A객체의 string 포인트에 저장된 내용만을 복사해서 B객체의 자유기억공간에 옮겨놓으면 됩니다.
#include<iostream>
using namespace std;
class Person {
public:
Person();
Person(const Person& rhs);
~Person() { delete name; }
void setID(int id) { this->id = id; }
void setName(string name) { *this->name = name; }
void show() { cout << *name << " " << id; }
private:
int id;
string* name;
};
Person::Person() {
id = 0;
name = new string;
}
Person::Person(const Person& rhs) {
id = rhs.id;
name = new string; // 동적할당
*name = *rhs.name; // 내용만 복사
}
int main() {
Person A;
A.setName("sangtae");
A.setID(23);
A.show();
Person B = A;
B.show();
}
복사생성자가 호출되는 상황
복사생성자가 호출되는 상황을 알아야 복사생성자함수를 만들 수 있겠죠?
위에서는 복사생성이 되는구나 라는 것을 쉽게 알 수 있었습니다.
주의해야할 것이 객체를 함수의 매개변수로 전달하고 객체를 반환하는 과정에서도 복사생성자가 호출된다는 것을 알아야 합니다.
복사생성자가 호출되는 상황
1. 새로운 객체를 같은 클래스 타입의 기존 객체와 똑같이 초기화 할 때
2. 객체가 함수에 인자로 전달될 때
3. 함수가 객체를 반환값으로 반환할 때
1번상황 예시
Person B = A;
Person B(A);
B = A; //이건 초기화가 아니므로 아닙니다.
2,3번 상황 예시
#include <iostream>
using namespace std;
class SimpleCat{
public:
SimpleCat();
SimpleCat(SimpleCat&); //복사생성자
~SimpleCat();
};
SimpleCat::SimpleCat()
{
cout << "Simple Cat Constructor...\n";
}
SimpleCat::SimpleCat(SimpleCat&)
{
cout << "Simple Cat Copy Constructor...\n";
}
SimpleCat::~SimpleCat()
{
cout << "Simple Cat Destructor...\n";
}
SimpleCat FunctionOne(SimpleCat theCat);
SimpleCat* FunctionTwo(SimpleCat* theCat);
int main()
{
cout << "Making a cat\n";
SimpleCat Frisky;
cout << "Calling FunctionOne\n";
FunctionOne(Frisky);
cout << "Calling FunctionTwo\n";
FunctionTwo(&Frisky);
return 0;
}
SimpleCat FunctionOne(SimpleCat theCat)
{
cout << "Function One. Returning...\n";
return theCat;
}
SimpleCat* FunctionTwo(SimpleCat* theCat)
{
cout << "Function Two. Returning...\n";
return theCat;
}
함수에 매개변수로 객체를 전달할 때 참조나 포인터를 사용하지 않으면 값 전달 방식(call by value)으로 전달되게 됩니다. 그렇게 함수안에서만 생명력을 가진 객체는 반환시에도 밖으로 나갈 수 없기에 자신의 객체를 복사한 후 반환합니다. 이렇게 함수 안에서 값 전달 방식으로 객체를 전달 시 복사생성자가 2번 호출되므로 이런 상황을 놓치지 않고 있다면 복사생성자를 호출해주는 것이 중요합니다.
'Programming > C,C++' 카테고리의 다른 글
오버라이딩을 통한 다형성의 실현(virtual 개념, 오버라이딩 개념) (0) | 2022.03.01 |
---|---|
참조리턴 (0) | 2022.02.23 |
연산자 중복 (0) | 2022.02.22 |
static 멤버(정적멤버) (0) | 2022.02.21 |
함수중복(fuction overloading) (0) | 2022.02.19 |