삶의 공유

C++ 상속에서의 복사 생성자와 대입 연산자 완전 정리 본문

Programing언어/C++

C++ 상속에서의 복사 생성자와 대입 연산자 완전 정리

dkrehd 2025. 10. 18. 22:04
728x90
반응형

📘 C++ 상속에서의 복사 생성자와 대입 연산자 완전 정리

— 왜 Base 클래스의 복사 동작을 명시적으로 호출해야 할까?

C++에서 상속 관계를 사용할 때, 파생 클래스(Derived)기반 클래스(Base) 의 멤버까지 올바르게 복사되려면
복사 생성자와 대입 연산자를 직접 구현하고, 반드시 Base의 복사 동작을 명시적으로 호출해야 합니다.

그렇지 않으면, 상위 클래스의 값이 복사되지 않거나, 예기치 않은 초기화(쓰레기 값)가 발생할 수 있습니다.

 


 

🧩 1. 기본 예제 — 문제의 시작

 
#include <print>

class Base {
    int bm;
public:
    Base() = default;
    Base(int n) : bm{n} {}
    int get_base_member() const { return bm; }
};

class Derived : public Base {
    int dm;
public:
    Derived(int n1, int n2) : Base{n1}, dm{n2} {}
};

int main() {
    Derived d1(10, 20);
    Derived d2 = d1;   // 복사 생성자 호출
    std::println("{}", d2.get_base_member());
}
 

💡 이 코드의 동작 분석

  • Derived d2 = d1;
    → 컴파일러가 자동으로 디폴트 복사 생성자를 만듭니다.
  • 하지만 이때 디폴트 복사 생성자는 다음과 같이 동작합니다:
 
Derived(const Derived& other)
    : Base(),          // ❌ Base() — 기본 생성자 호출
      dm(other.dm) {}
 
 

즉, Base(other)가 아니라 Base()가 호출되어
Base의 멤버(bm)가 복사되지 않습니다.

결과적으로 d2.get_base_member()는 의도치 않은 쓰레기 값을 반환하게 됩니다.

 


 

🧠 2. 복사 생성자 직접 구현하기 (Deep Copy for Inheritance)

해결 방법은 간단합니다.
파생 클래스의 복사 생성자에서 기반 클래스의 복사 생성자를 명시적으로 호출하면 됩니다.

 
class Derived : public Base {
    int dm;
public:
    Derived(int n1, int n2) : Base{n1}, dm{n2} {}

    // ✅ 올바른 복사 생성자 구현
    Derived(const Derived& other)
        : Base(other),     // 🔹 기반 클래스의 복사 생성자 명시적 호출
          dm(other.dm)
    {
        std::println("Derived copy constructor");
    }
};

 

이제 다시 실행해보면:

int main() {
    Derived d1(10, 20);
    Derived d2 = d1;
    std::println("{}", d2.get_base_member()); // ✅ 10 출력
}
 

✅ 정리

  • Base(other)를 명시적으로 호출하면 Base의 복사 생성자가 실행되어
    bm 값이 정상적으로 복사됩니다.
  • 만약 Base()로 남겨두면 기본 생성자만 호출되어 값이 초기화되지 않음.

 


 

⚙️ 3. 대입 연산자(operator=)에서도 동일한 문제

복사 생성자뿐 아니라, 대입 연산자에서도 같은 문제가 발생합니다.
파생 클래스의 대입 연산자에서 기반 클래스의 대입 연산자를 직접 호출해야 합니다.

❌ 잘못된 예시

class Derived : public Base {
    int dm;
public:
    Derived& operator=(const Derived& other) {
        if (this == &other) return *this;
        dm = other.dm;
        return *this; // ❌ Base 부분 복사 안 됨
    }
};

 

int main() {
    Derived d1(10, 20);
    Derived d2(0, 0);
    d2 = d1; // 대입 연산자 호출
    std::println("{}", d2.get_base_member()); // ⚠️ 여전히 0 출력 (bm 미복사)
}

 

→ Base의 멤버(bm)가 복사되지 않았기 때문에 값이 그대로 남아있습니다.

 


 

✅ 4. 올바른 대입 연산자 구현

 
class Derived : public Base {
    int dm;
public:
    Derived& operator=(const Derived& other) {
        if (this == &other) return *this;

        Base::operator=(other);   // ✅ 기반 클래스의 대입 연산자 명시적 호출
        dm = other.dm;            // ✅ 파생 클래스 멤버 복사

        return *this;
    }
};
 
 

이제 다음 코드를 실행하면:

 
int main() {
    Derived d1(10, 20);
    Derived d2(0, 0);

    d2 = d1;
    std::println("{}", d2.get_base_member()); // ✅ 10 출력
}
 
 

💬 동작 원리

  • Base::operator=(other) 가 실행되면서
    Base 클래스 내부의 bm 멤버가 정상 복사됩니다.
  • 이후 Derived의 멤버 dm 도 별도로 복사되어
    전체 객체가 완전하게 동일해집니다.

 


 

🧩 5. 정리된 전체 코드

 
#include <print>

class Base {
    int bm;
public:
    Base() = default;
    Base(int n) : bm{n} {}
    Base(const Base& other) : bm(other.bm) {}
    Base& operator=(const Base& other) {
        if (this == &other) return *this;
        bm = other.bm;
        return *this;
    }
    int get_base_member() const { return bm; }
};

class Derived : public Base {
    int dm;
public:
    Derived(int n1, int n2) : Base{n1}, dm{n2} {}

    // 복사 생성자
    Derived(const Derived& other)
        : Base(other), dm(other.dm) {}

    // 대입 연산자
    Derived& operator=(const Derived& other) {
        if (this == &other) return *this;
        Base::operator=(other);
        dm = other.dm;
        return *this;
    }
};
 
 

📚 6. 상속 구조에서의 복사 규칙 요약

구분잘못된 구현올바른 구현결과

 

 

복사 생성자 : Base() : Base(other) Base 멤버도 복사됨
대입 연산자 dm = other.dm; Base::operator=(other); dm = other.dm; Base + Derived 모두 복사됨

 


 

🧠 7. 핵심 요약

  • 상속 구조에서 복사 생성자 / 대입 연산자 를 직접 구현할 때는 반드시
    기반 클래스의 복사 동작을 명시적으로 호출(Base(other), Base::operator=) 해야 한다.
  • 그렇지 않으면, Base의 멤버는 복사되지 않거나 초기화되지 않은 상태로 남게 된다.
  • Base 부분은 Base가, Derived 부분은 Derived가 책임진다는 원칙을 기억하자.
반응형