Notice
Recent Posts
Recent Comments
Link
삶의 공유
C++ 상속에서의 복사 생성자와 대입 연산자 완전 정리 본문
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가 책임진다는 원칙을 기억하자.
반응형
'Programing언어 > C++' 카테고리의 다른 글
| C++ 벡터의 capacity와 size 완전 이해하기 (0) | 2025.11.18 |
|---|---|
| C++ STL 핵심 파헤치기: vector, list, deque... 대체 언제, 무엇을 써야 할까? (0) | 2025.11.03 |
| C++에서 Shallow Copy와 Deep Copy 완벽 정리 — 복사의 진짜 차이를 이해하자 (0) | 2025.10.16 |
| C++ ADL (Argument Dependent Lookup) 완벽 정리 (0) | 2025.10.15 |
| C++ 함수 객체(Function Object) 완전 정리 (0) | 2025.09.29 |