1. 복사 생성자는 다른 개체를 인자로 받는 생성자이다.
여기서 c2는 따로 정의한 생성자 C(int x, int y) 와 달리 인자로 클래스 C의 객체를 받았는데 c1과 동일한 객체가 생성되었다.
이는 복사 생성자가 불린 것으로, 따로 사용자가 제공하지 않으면 컴파일러가 기본적으로 멤버 변수를 그대로 복사하는 복사 생성자를 제공해주기 때문이다.
복사 생성자를 정의할 때에는 아래처럼 한다. 인자를 const C&이 아니라 C 타입으로 받을 경우 에러가 발생하는데, 이유는 아래에서 알 수 있다.
2. 복사 생성자가 호출되는 경우
복사 생성자는 위에서처럼 동일 클래스 객체로 초기화 될 때,
함수에 인자로 call-by-value로 전달할 때,
함수에서 리턴할 때 불리게 된다.
위에서 복사 생성자를 정의하면서 인자로 call-by-value로 넘기게 될 경우 아직 정의 하지 않은 복사 생성자를 사용하기에 에러가 발생한다.
함수 인자로 call-by-value로 전달하게 되면 매번 복사생성자가 호출되기에 오버헤드가 발생하게 된다.
따라서 일반적인 경우 참조로 전달하게 된다.
리턴할 때 값을 리턴하게 되면, 복사 생성자가 호출되게 된다.
따라서 C 객체를 리턴할 때 위처럼 구현하게 되면 복사 생성자가 두번 불리게 된다.
이를 막기 위해 아래처럼 임시 객체를 리턴하게 되면 복사 생성자 호출 횟수를 줄일 수 있는데, 이를 Return Value Optimization (RVO) 라 한다.
단 컴파일러가 위의 함수도 자동으로 복사 생성자를 한번만 불리게 바꿔주는데, 이를 Named Return Value Optimization (NRVO)라 한다.
아니면 참조를 리턴할 경우 복사생성자 호출을 막을 수 있다.
단, 이 경우에는 지역 변수의 참조를 리턴하지 않게 주의한다. (지역 변수는 함수가 끝나면서 파괴된다)
3. 멤버 변수가 포인터인 경우 디폴트 복사 생성자
멤버 변수가 포인터인 경우 컴파일러가 만든 복사 생성자는 포인터 값을 그대로 카피하기에 결과적으로 얕은 복사가 일어나게 된다.
위의 코드에서는 twin이 p를 인자로 받는 디폴트 복사 생성자를 통해 복사 되게 되는데, twin이 있는 bracket이 끝나는 시점에 twin의 소멸자가 불리게 되고 동시에 p.name이 가리키는 자원도 delete 되어 p의 데이터는 더이상 쓸모 없게 된다.
얕은 복사가 일어나면 안 되는 경우에는 다음과 같은 해결책을 사용해야 한다.
1) 깊은 복사
깊은 복사를 통해 Person을 구현한다. 이때 디폴트 복사 생성자와 대입 연산자를 구현해주면 된다.
2) 참조 계수
얕은 복사를 하되, 현재 name을 가리키고 있는 포인터의 수를 기록한다. 그리고 소멸자에서는 포인터를 아무도 가리키지 않을 때만 자원을 파괴한다.
깊은 복사를 이용하면 구현은 단순하지만 같은 데이터를 가진 자원이 중복되어서 존재하는 경우는 메모리 낭비가 많이 존재하게 된다.
반면 참조 계수는 같은 데이터는 한 자원만 존재하여 메모리 최적화가 되지만, 구현이 복잡하여 잘 만들어야 한다. 위의 구현에서는 p.name을 수정할 경우에 대한 구현이 되어있지 않다.
3) 복사 생성자의 제거
아래처럼 복사 생성자를 제거해버리는 방법도 있다.
복사 생성자를 private에 놓는 경우 복사 생성하는 부분에서 "링커 에러"를 발생한다.
C++11에서 추가된 delete 구문으로 복사 생성자를 삭제하는 경우 "컴파일 에러"가 발생한다.
4. 소유권 이전
move 생성자를 이용하는 방법.
'프로그래밍 > C++' 카테고리의 다른 글
정적 멤버 함수 (Static member function) (0) | 2017.11.13 |
---|---|
정적 멤버 변수 (Static member variable) (0) | 2017.11.13 |
클래스 초기화 방법 정리 (0) | 2017.11.08 |
생성자/소멸자를 protected, private에 놓는 경우 정리 (0) | 2017.11.08 |
선언부와 구현부를 분리할 때 주의해야 할 것 정리 (0) | 2017.11.08 |