Language/C++

📌 Chapter 05 - 복사 생성자(Copy Constructor)

e-cko 2025. 8. 11. 17:39
반응형

1. 기본 복사 생성자 (Default Copy Constructor)

1) 자동 생성되는 복사 생성자

  • 복사 생성자는 객체를 다른 객체로 초기화할 때 호출되는 생성자이다.
  • 사용자가 직접 정의하지 않으면 컴파일러가 기본 복사 생성자를 자동으로 만들어준다.
  • 기본 복사 생성자의 형태는 클래스명(const 클래스명 &객체) 형식이다.

예시:

Ex(const Ex& cp) : n(cp.n) {}

  • 아래 코드처럼 객체를 복사하면, 내부적으로 복사 생성자가 호출된다.

Ex ex2 = ex; // Ex ex2(ex); 와 동일하게 해석됨


2) 얕은 복사(Shallow Copy)의 문제점 ⚠️

  • 얕은 복사, 포인터 멤버의 주소 값만 복사하는 방식이다.
  • , 두 객체가 동일한 메모리 주소를 공유하게 된다.

문제점:
1️
두 객체 중 하나가 소멸되면 공유된 메모리도 해제되어 다른 객체가 사용할 수 없게 된다.
2️
이로 인해 double free, 메모리 접근 오류 등이 발생할 수 있다.


3) explicit 키워드의 효과 ✳️

  • 복사 생성자에 explicit 키워드를 붙이면 암시적 변환을 막을 수 있다.

explicit Ex(const Ex& cp);

효과:

  • Ex ex = 1; 처럼 암시적 변환을 통한 객체 생성이 불가능해진다.
  • 이는 명확한 타입 안정성을 보장한다.

2. 사용자 정의 복사 생성자

1) 복사 생성자의 정의 방법

  • 복사 생성자는 다음 형식으로 직접 정의할 수 있다.

Ex(const Ex& cp) { n = cp.n; }

  • 복사 생성자는 반드시 참조형으로 인자를 받아야 한다.
    ·
    만약 값으로 인자를 받으면, 복사 생성자가 자기 자신을 호출하게 되어 무한 루프에 빠진다.
    ·
    컴파일러는 이를 감지하고 에러를 발생시킨다.
  • const 키워드를 붙이지 않아도 되지만, 객체 복사의 의미를 명확히 하기 위해 붙이는 것이 좋다.

2) 깊은 복사 (Deep Copy) ⛏️

  • 깊은 복사, 동적 할당된 메모리까지 복사하는 방식이다.
  • 얕은 복사와 달리, 각 객체가 독립된 메모리 공간을 소유하므로 안전하다.

깊은 복사를 위한 복사 생성자 예:

Ex(const Ex& cp) {

    n = new char[strlen(cp.n) + 1];  // 새로운 메모리 공간 확보

    strcpy(n, cp.n);                 // 내용 복사

}


3. 복사 생성자가 호출되는 시점

복사 생성자는 다음과 같은 상황에서 호출된다:

1) 객체 초기화 시

Ex a; // 일반 생성자 호출 → a 생성

Ex b = a; // Ex b(a);로 묵시적 형변환, 클래스 Ex에 정의된 복사 생성자가 호출되어 b의 멤버를 a로부터 복사함.

2) 함수에 객체를 값으로 전달(Call by value) 할 때

void ShowEx(Ex ex);  // 함수 외부에서 전달된 객체를 ex라는 새로운 지역 변수로 복사하여 전달받음

3) 함수에서 객체를 반환할 때

Ex GetEx(Ex temp) {

    return temp; 

    // 복사 생성자 호출 (임시 객체 생성)

    // 컴파일러에 따라 RVO(Return Value Optimization) 또는 Copy Elision(복사 생략)이 발생할 수 있다.

    // 이 경우 복사 생성자가 호출되지 않고 최적화로 생략된다.

}

함수가 객체를 반환할 때, 일반적으로는 복사 생성자가 호출되어 반환용 임시 객체가 만들어진다.

하지만 최신 컴파일러는 RVO(반환값 최적화) 또는 복사 생략을 적용해,

👉 임시 객체를 복사하지 않고 직접 생성하여 성능을 개선한다.

따라서 복사 생성자 호출이 생략될 수도 있음을 반드시 기억해야 한다.

 

4) 임시 객체의 소멸과 참조자 바인딩

const Ex& ref = Ex(1);  // 임시 객체를 const 참조자가 참조함

• Ex(1)은 임시 객체를 생성하는 표현이다.

임시 객체는 보통 해당 문장이 끝난 후 바로 소멸한다.

하지만, 위 코드처럼 임시 객체가 const 참조자에 바인딩되면,

👉 임시 객체의 수명이 참조자의 수명과 동일하게 연장된다.

 

, 임시 객체는 ref가 선언된 블록이 종료될 때까지 살아남는다.


마무리 정리

1️ 복사 생성자는 객체를 복사할 때 호출되는 특수 생성자이다.
2️
기본 복사 생성자는 얕은 복사를 수행하며, 동적 메모리 사용 시 문제를 발생시킬 수 있다.
3️
깊은 복사를 위해서는 사용자가 복사 생성자를 직접 정의해야 한다.
4️
복사 생성자는 참조형으로 받아야 하며, const를 붙이는 것이 권장된다.
5️
함수 인자 전달, 반환 시점 등 다양한 순간에 복사 생성자가 호출된다.
6️
explicit 키워드는 암시적 변환을 방지하여 객체 생성의 안정성을 높인다.


💻 예제 코드

#include <iostream>

#include <cstring>  // strlen, strcpy 사용

 

class Ex {

    char* n;  // 문자열 저장용 포인터

public:

    // 생성자

    Ex(const char* str) {

        n = new char[strlen(str) + 1];  // 메모리 동적 할당

        strcpy(n, str);                  // 문자열 복사

        std::cout << "일반 생성자 호출, 주소: " << this << std::endl;

    }

 

    // 복사 생성자 (깊은 복사)

    Ex(const Ex& cp) {

        n = new char[strlen(cp.n) + 1];

        strcpy(n, cp.n);

        std::cout << "복사 생성자 호출, 주소: " << this << std::endl;

    }

 

    // 소멸자

    ~Ex() {

        std::cout << "소멸자 호출, 주소: " << this << std::endl;

        delete[] n;

    }

 

    void Show() const {

        std::cout << "문자열: " << n << std::endl;

    }

};

 

// 함수 인자 전달 시 복사 생성자 호출 확인용

Ex ExFuncObj(Ex ob) {

    std::cout << "함수 매개변수로 복사된 객체 주소: " << &ob << std::endl;

    ob.Show();

    return ob;  // 반환 시 복사 생성자 호출 가능 (최적화로 생략 가능)

}

 

// 함수 반환 예시

Ex CreateEx() {

    Ex temp("임시 객체");

    return temp;  // 복사 생성자 호출될 수 있음 (컴파일러 최적화 가능)

}

 

int main() {

    std::cout << "=== Ex 클래스 테스트 ===" << std::endl;

 

    Ex a("원본");

    Ex b = a;  // 복사 생성자 호출

 

    std::cout << "\n-- 함수 인자 전달 --" << std::endl;

    ExFuncObj(a);  // 함수 매개변수 복사 생성자 호출

 

    std::cout << "\n-- 함수 반환 --" << std::endl;

    Ex c = CreateEx();  // 함수 반환 시 복사 생성자 호출 (최적화 가능)

 

    std::cout << "\n-- 임시 객체 참조 --" << std::endl;

    const Ex& ref = Ex("임시 참조");  // 임시 객체 수명 연장

    ref.Show();

 

    return 0;

}📤 출력 예시

=== Ex 클래스 테스트 ===

일반 생성자 호출, 주소: 0x7ffeefbff5b0

복사 생성자 호출, 주소: 0x7ffeefbff5a0

 

-- 함수 인자 전달 --

복사 생성자 호출, 주소: 0x7ffeefbff590

함수 매개변수로 복사된 객체 주소: 0x7ffeefbff590

문자열: 원본

복사 생성자 호출, 주소: 0x7ffeefbff580

소멸자 호출, 주소: 0x7ffeefbff590

소멸자 호출, 주소: 0x7ffeefbff580

 

-- 함수 반환 --

일반 생성자 호출, 주소: 0x7ffeefbff570

복사 생성자 호출, 주소: 0x7ffeefbff560

복사 생성자 호출, 주소: 0x7ffeefbff550

소멸자 호출, 주소: 0x7ffeefbff570

소멸자 호출, 주소: 0x7ffeefbff560

소멸자 호출, 주소: 0x7ffeefbff550

 

-- 임시 객체 참조 --

일반 생성자 호출, 주소: 0x7ffeefbff540

문자열: 임시 참조

소멸자 호출, 주소: 0x7ffeefbff540

소멸자 호출, 주소: 0x7ffeefbff5a0

소멸자 호출, 주소: 0x7ffeefbff5b0


✳️ 복사 생성자는 클래스 설계에서 자주 등장하는 핵심 요소이다.
포인터를 멤버로 가지는 경우 반드시 직접 정의해서 메모리 안정성을 확보해야 한다.

반응형

 

반응형