1. 복사 생성자 (Copy Constructor)
#include <iostream>
class Point {
public:
int x;
int y;
Point() : x(0), y(0) {}
Point(int a, int b) : x(a), y(b) {}
};
int main() {
Point p1; // Point() 생성자 ok
Point p2(1, 2); // Point(int a, int b) 생성자 ok
Point p3(1); // Point(int a) 생성자 error
Point p4(p2); // Point(Point p) 생성자 ok
}
위의 코드에서 Point p4(p2); 의 경우 생성자가 없으니 error가 나야 할 것 같지만
막상 컴파일 해보면 에러 없이 정상 실행 된다.
그 말은, Point 클래스 내에 생성자가 있기는 하다는 이야기다.
자기 자신을 인자로 했을 때 생성자가 없다면 컴파일러가 아래와 같은 생성자를 만들어 준다.
Point (const Point& p) : x(p.x), y(p.y) {}
① 복사 생성자란?
=> 자신과 동일한 타입 한 개를 인자로 가지는 생성자
② 사용자가 복사 생성자를 만들지 않으면
=> 컴파일러가 제공
=> 디폴트 복사 생성자 (default copy constructor)
=> 모든 멤버를 복사(bitwise copy) 한다.
③ 참고 사항
=> 생성자가 하나도 없으면 만들어 주는게 아니다. -> 디폴트 생성자
=> 복사 생성자 자체가 없다면 컴파일러가 만들어준다.
2. 복사 생성자가 호출되는 3가지 경우
#include <iostream>
class Point {
public:
int x;
int y;
Point(int a, int b) : x(a), y(b) {
std::cout << "ctor" << std::endl;
}
Point(const Point& p) : x(p.x), y(p.y) {
std::cout << "copyt ctor" << std::endl;
}
};
int main() {
Point p1(1, 2); // 생성자
Point p2(p1); // 복사 생성자
Point p3{p1}; // C++11 일관된 초기화 - 직접 초기화
Point p4 = {p1}; // 복사 초기화
Point p5 = p1; // 복사 초기화
}
① 자신과 동일한 타입의 객체로 초기화 될 때
=> Point p2(p1);
=> Point p2{p1}; -> 일관된 초기화
=> Point p2 = p1; -> explicit이 아닌 경우에만
#include <iostream>
class Point {
public:
int x;
int y;
Point(int a, int b) : x(a), y(b) {
std::cout << "ctor" << std::endl;
}
Point(const Point& p) : x(p.x), y(p.y) {
std::cout << "copyt ctor" << std::endl;
}
};
// void tmp(Point pt) {} // Point pt = p1
void tmp(const Point& pt) {} // const Point& pt = p1
int main() {
Point p1(1, 2);
tmp(p1);
}
tmp함수는 인자로 p1을 받는다. 받는 모습을 보니 call by value로 받고 있다.
call by value는 p1의 복사본을 받게 된다.
그럼 결국 코드는 Point pt = p1; 이라는 코드로 받게 될 것이다.
② 함수 인자를 call by value로 받을 경우
=> 함수 인자를 const reference로 사용 하면 복사본을 만들지 않으므로 복사 생성자가 호출되지 않는다.
#include <iostream>
class Point {
public:
int x;
int y;
Point(int a = 0, int b = 0) : x(a), y(b) {
std::cout << "ctor" << std::endl;
}
Point(const Point& p) : x(p.x), y(p.y) {
std::cout << "copyt ctor" << std::endl;
}
};
Point p; // 생성자
Point tmp() { // 값 타입 반환
return p;
}
int main() {
tmp();
}
③ 함수가 객체를 값으로 반환 할 때
=> 참조로 반환 하면 리턴 용 임시객체가 생성되지 않는다.
=> 단, 지역변수는 참조로 반환 하면 안된다.
3. 디폴트 복사 생성자의 문제점
#include <iostream>
class Point {
public:
int x;
int y;
Point(int a = 0, int b = 0) : x(a), y(b) { }
};
int main() {
Point p1(1, 2);
Point p2(p1);
}
① 객체가 자신과 동일한 타입의 객체로 초기화 될 때
=> 복사 생성자가 사용된다.
=> 사용자가 만들지 않은 경우 디폴트 복사 생성자가 사용된다.
=> 디폴트 복사 생성자는 모든 멤버를 복사해 준다.
② 디폴트 복사 생성자가 모든 멤버를 복사 해주는 것은
=> 편리한 경우도 있지만
=> 문제가 되는 경우도 있다.
#include <iostream>
#include <cstring>
class Person {
char* name;
int age;
public:
Person(const char* n, int a) : age(a) {
name = new char[strlen(n) + 1];
strcpy(name, n);
}
};
int main() {
Person p1("kim", 20);
}
Person이라는 클래스를 만들었다.
위의 코드는 나이는 그대로 초기화하면 되지만 이름은 초기화 할 때 신경을 썼다.
이유는 멤버 데이터는 char* name; 이 아닌 char name[6]으로 하면 이름은 6자밖에 안들어가게 되고
char name[1000]; 으로하면 메모리 낭비가 발생하기에 포인터로 하고 인자를 받게 되면
생성자에서 그 길이만큼 메모리를 지정하고 복사하는 방식으로 했다.
또, new로 자원을 할당했으니 소멸자를 통해 자원을 해지시켜주어야 한다.
#include <iostream>
#include <cstring>
class Person {
char* name;
int age;
public:
Person(const char* n, int a) : age(a) {
name = new char[strlen(n) + 1];
strcpy(name, n);
}
~Person() { delete name; }
};
int main() {
Person p1("kim", 20);
Person p2 = p1;
}
이제 위의 코드는 이름 길이의 제한을 없애고, 자원을 할당하고 해지를 해주는 잘 만든 클래스가 되었다.
하지만 main함수에서 Person p2 = p1; 이라고 적는다면 문제가 발생한다.
어떤 문제일까?
먼저 위의 그림에서 name은 포인터로 1000번 주소를 가르키고
1000번 주소에는 크기에 맞는 메모리를 할당하여 kim이라는 문자열을 복사하고 있다.
p1이 파괴되면 멤버 데이터도 파괴되므로 아무 문제가 없는데
p2 = p1;을 쓰게 되면 문제가 있다.
모든 객체는 생성될 때 생성자를 호출하게 된다.
p2 = p1; 은 복사 생성자를 호출하게 되고, 위의 코드에서 복사 생성자를 따로 만들지 않았으므로
컴파일러가 디폴트 복사 생성자를 만들어준다.
디폴트 복사 생성자는 p1의 모든 멤버를 p2가 복사하게 된다. 그렇다면
한 메모리를 두 객체가 바라보는 위와 같은 형태가 되게 된다.
위의 코드에선 p2가 먼저 생성되었으니 p2가 먼저 파괴될 것이다.
파괴될 땐 소멸자를 호출할 것이고, 소멸자를 통해 name의 메모리를 해지시켰다.
하지만 p1은 여전히 살아있고 p1이 name을 쓰면 런타임 에러가 날 수 있다.
또 p1이 사라질 때 delete를 할텐데 이미 사라진 메모리를 delete를 하므로 에러가 난다.
③ 얕은 복사 (Shallow Copy)
=> 클래스 안에 포인터 멤버가 있을 때 디폴트 복사 생성자가
=> 메모리 자체를 복사하지 않고, 주소만 복사하는 현상
=> 개발자가 직접 복사 생성자를 만들어야 한다.
=> 어떻게 복사할 것인가? 다양한 방법 존재.
'프로그래밍 > C++' 카테고리의 다른 글
[C++] static member data (0) | 2019.11.14 |
---|---|
[C++] 객체의 복사 방법 (0) | 2019.11.13 |
[C++] explicit 생성자 (0) | 2019.11.06 |
[C++] 초기화 리스트 (member initializer list) (4) | 2019.11.05 |
[C++] 소멸자 (destructor) (0) | 2019.11.04 |