상속과 다형성
상속과 다형성¶
1. 상속이란?¶
상속은 기존 클래스(부모)의 속성과 메서드를 새 클래스(자식)가 물려받는 것입니다.
#include <iostream>
#include <string>
// 부모 클래스 (기반 클래스)
class Animal {
public:
std::string name;
void eat() {
std::cout << name << "이(가) 먹습니다." << std::endl;
}
void sleep() {
std::cout << name << "이(가) 잠을 잡니다." << std::endl;
}
};
// 자식 클래스 (파생 클래스)
class Dog : public Animal {
public:
void bark() {
std::cout << name << ": 멍멍!" << std::endl;
}
};
class Cat : public Animal {
public:
void meow() {
std::cout << name << ": 야옹!" << std::endl;
}
};
int main() {
Dog dog;
dog.name = "바둑이";
dog.eat(); // 상속받은 메서드
dog.bark(); // Dog만의 메서드
Cat cat;
cat.name = "나비";
cat.sleep(); // 상속받은 메서드
cat.meow(); // Cat만의 메서드
return 0;
}
2. 상속 접근 지정자¶
class Base {
public:
int publicVar;
protected:
int protectedVar;
private:
int privateVar;
};
// public 상속 (가장 일반적)
class PublicDerived : public Base {
// publicVar → public
// protectedVar → protected
// privateVar → 접근 불가
};
// protected 상속
class ProtectedDerived : protected Base {
// publicVar → protected
// protectedVar → protected
// privateVar → 접근 불가
};
// private 상속
class PrivateDerived : private Base {
// publicVar → private
// protectedVar → private
// privateVar → 접근 불가
};
접근 지정자 요약¶
| 부모 멤버 | public 상속 | protected 상속 | private 상속 |
|---|---|---|---|
| public | public | protected | private |
| protected | protected | protected | private |
| private | 접근 불가 | 접근 불가 | 접근 불가 |
3. 생성자와 소멸자 호출 순서¶
#include <iostream>
class Base {
public:
Base() {
std::cout << "Base 생성자" << std::endl;
}
~Base() {
std::cout << "Base 소멸자" << std::endl;
}
};
class Derived : public Base {
public:
Derived() {
std::cout << "Derived 생성자" << std::endl;
}
~Derived() {
std::cout << "Derived 소멸자" << std::endl;
}
};
int main() {
Derived d;
return 0;
}
출력:
Base 생성자
Derived 생성자
Derived 소멸자
Base 소멸자
부모 생성자 호출¶
#include <iostream>
#include <string>
class Person {
protected:
std::string name;
int age;
public:
Person(std::string n, int a) : name(n), age(a) {
std::cout << "Person 생성자" << std::endl;
}
};
class Student : public Person {
private:
int studentId;
public:
// 부모 생성자 호출
Student(std::string n, int a, int id)
: Person(n, a), studentId(id) { // 초기화 리스트에서 호출
std::cout << "Student 생성자" << std::endl;
}
void show() const {
std::cout << "이름: " << name << ", 나이: " << age
<< ", 학번: " << studentId << std::endl;
}
};
int main() {
Student s("Alice", 20, 20210001);
s.show();
return 0;
}
4. 함수 오버라이딩¶
자식 클래스에서 부모의 함수를 재정의합니다.
#include <iostream>
class Animal {
public:
void speak() {
std::cout << "동물이 소리를 냅니다." << std::endl;
}
};
class Dog : public Animal {
public:
void speak() { // 오버라이딩
std::cout << "멍멍!" << std::endl;
}
};
class Cat : public Animal {
public:
void speak() { // 오버라이딩
std::cout << "야옹!" << std::endl;
}
};
int main() {
Animal a;
Dog d;
Cat c;
a.speak(); // 동물이 소리를 냅니다.
d.speak(); // 멍멍!
c.speak(); // 야옹!
return 0;
}
부모 함수 호출¶
class Dog : public Animal {
public:
void speak() {
Animal::speak(); // 부모 함수 호출
std::cout << "멍멍!" << std::endl;
}
};
5. 가상 함수 (virtual)¶
런타임에 적절한 함수를 호출합니다 (동적 바인딩).
문제: 정적 바인딩¶
#include <iostream>
class Animal {
public:
void speak() {
std::cout << "동물 소리" << std::endl;
}
};
class Dog : public Animal {
public:
void speak() {
std::cout << "멍멍!" << std::endl;
}
};
int main() {
Dog dog;
Animal* ptr = &dog;
ptr->speak(); // "동물 소리" (문제!)
return 0;
}
해결: virtual 키워드¶
#include <iostream>
class Animal {
public:
virtual void speak() { // virtual 추가
std::cout << "동물 소리" << std::endl;
}
};
class Dog : public Animal {
public:
void speak() override { // override (C++11, 선택적)
std::cout << "멍멍!" << std::endl;
}
};
class Cat : public Animal {
public:
void speak() override {
std::cout << "야옹!" << std::endl;
}
};
int main() {
Dog dog;
Cat cat;
Animal* ptr1 = &dog;
Animal* ptr2 = &cat;
ptr1->speak(); // 멍멍! (올바름!)
ptr2->speak(); // 야옹! (올바름!)
return 0;
}
override 키워드 (C++11)¶
class Base {
public:
virtual void foo(int x) {}
};
class Derived : public Base {
public:
void foo(int x) override {} // OK
// void foo(double x) override {} // 에러! 시그니처 불일치
// void bar() override {} // 에러! 부모에 bar 없음
};
final 키워드 (C++11)¶
class Base {
public:
virtual void foo() final {} // 더 이상 오버라이드 불가
};
class Derived : public Base {
public:
// void foo() override {} // 에러! final 함수
};
// 클래스 상속 금지
class FinalClass final {};
// class Derived2 : public FinalClass {}; // 에러!
6. 가상 소멸자¶
기반 클래스의 소멸자는 반드시 virtual이어야 합니다.
#include <iostream>
class Base {
public:
Base() { std::cout << "Base 생성" << std::endl; }
virtual ~Base() { std::cout << "Base 소멸" << std::endl; } // virtual!
};
class Derived : public Base {
private:
int* data;
public:
Derived() {
data = new int[100];
std::cout << "Derived 생성" << std::endl;
}
~Derived() {
delete[] data;
std::cout << "Derived 소멸" << std::endl;
}
};
int main() {
Base* ptr = new Derived();
delete ptr; // virtual 덕분에 Derived 소멸자도 호출됨
return 0;
}
출력:
Base 생성
Derived 생성
Derived 소멸
Base 소멸
7. 순수 가상 함수와 추상 클래스¶
순수 가상 함수¶
class Shape {
public:
// 순수 가상 함수 (= 0)
virtual double getArea() const = 0;
virtual double getPerimeter() const = 0;
virtual ~Shape() = default;
};
// Shape shape; // 에러! 추상 클래스는 인스턴스 생성 불가
추상 클래스 구현¶
#include <iostream>
#include <cmath>
class Shape {
public:
virtual double getArea() const = 0;
virtual double getPerimeter() const = 0;
virtual void draw() const = 0;
virtual ~Shape() = default;
};
class Circle : public Shape {
private:
double radius;
public:
Circle(double r) : radius(r) {}
double getArea() const override {
return M_PI * radius * radius;
}
double getPerimeter() const override {
return 2 * M_PI * radius;
}
void draw() const override {
std::cout << "원을 그립니다. 반지름: " << radius << std::endl;
}
};
class Rectangle : public Shape {
private:
double width, height;
public:
Rectangle(double w, double h) : width(w), height(h) {}
double getArea() const override {
return width * height;
}
double getPerimeter() const override {
return 2 * (width + height);
}
void draw() const override {
std::cout << "사각형을 그립니다. "
<< width << " x " << height << std::endl;
}
};
int main() {
Circle c(5);
Rectangle r(4, 3);
Shape* shapes[] = {&c, &r};
for (Shape* s : shapes) {
s->draw();
std::cout << " 넓이: " << s->getArea() << std::endl;
std::cout << " 둘레: " << s->getPerimeter() << std::endl;
}
return 0;
}
8. 다중 상속¶
여러 부모 클래스를 상속받을 수 있습니다.
#include <iostream>
class Flyable {
public:
void fly() {
std::cout << "날아갑니다." << std::endl;
}
};
class Swimmable {
public:
void swim() {
std::cout << "헤엄칩니다." << std::endl;
}
};
class Duck : public Flyable, public Swimmable {
public:
void quack() {
std::cout << "꽥꽥!" << std::endl;
}
};
int main() {
Duck duck;
duck.fly(); // Flyable로부터
duck.swim(); // Swimmable로부터
duck.quack(); // Duck 고유
return 0;
}
다이아몬드 문제¶
#include <iostream>
class Animal {
public:
int age;
};
class Mammal : public Animal {};
class Bird : public Animal {};
// Diamond 문제
class Bat : public Mammal, public Bird {
// age가 두 번 상속됨!
};
int main() {
Bat bat;
// bat.age = 5; // 에러! 모호함
bat.Mammal::age = 5; // 명시적 지정
bat.Bird::age = 10;
return 0;
}
가상 상속으로 해결¶
#include <iostream>
class Animal {
public:
int age;
};
class Mammal : virtual public Animal {}; // virtual 상속
class Bird : virtual public Animal {}; // virtual 상속
class Bat : public Mammal, public Bird {
// age가 하나만 존재
};
int main() {
Bat bat;
bat.age = 5; // OK!
std::cout << bat.age << std::endl;
return 0;
}
9. 인터페이스 패턴¶
순수 가상 함수만 있는 클래스입니다.
#include <iostream>
#include <string>
// 인터페이스
class Printable {
public:
virtual void print() const = 0;
virtual ~Printable() = default;
};
class Serializable {
public:
virtual std::string serialize() const = 0;
virtual void deserialize(const std::string& data) = 0;
virtual ~Serializable() = default;
};
// 여러 인터페이스 구현
class Document : public Printable, public Serializable {
private:
std::string content;
public:
Document(const std::string& c) : content(c) {}
void print() const override {
std::cout << "문서 내용: " << content << std::endl;
}
std::string serialize() const override {
return "DOC:" + content;
}
void deserialize(const std::string& data) override {
if (data.substr(0, 4) == "DOC:") {
content = data.substr(4);
}
}
};
int main() {
Document doc("Hello, World!");
Printable* p = &doc;
p->print();
Serializable* s = &doc;
std::cout << s->serialize() << std::endl;
return 0;
}
10. RTTI (런타임 타입 정보)¶
dynamic_cast¶
#include <iostream>
class Base {
public:
virtual ~Base() = default;
};
class Derived : public Base {
public:
void derivedOnly() {
std::cout << "Derived 전용 함수" << std::endl;
}
};
int main() {
Base* base = new Derived();
// 안전한 다운캐스팅
Derived* derived = dynamic_cast<Derived*>(base);
if (derived) {
derived->derivedOnly();
}
Base* base2 = new Base();
Derived* derived2 = dynamic_cast<Derived*>(base2);
if (derived2 == nullptr) {
std::cout << "캐스팅 실패" << std::endl;
}
delete base;
delete base2;
return 0;
}
typeid¶
#include <iostream>
#include <typeinfo>
class Animal {
public:
virtual ~Animal() = default;
};
class Dog : public Animal {};
class Cat : public Animal {};
int main() {
Animal* a1 = new Dog();
Animal* a2 = new Cat();
std::cout << typeid(*a1).name() << std::endl; // Dog 관련
std::cout << typeid(*a2).name() << std::endl; // Cat 관련
if (typeid(*a1) == typeid(Dog)) {
std::cout << "a1은 Dog입니다." << std::endl;
}
delete a1;
delete a2;
return 0;
}
11. 요약¶
| 개념 | 설명 |
|---|---|
class Derived : public Base |
상속 |
virtual |
가상 함수 (동적 바인딩) |
override |
오버라이드 명시 |
final |
상속/오버라이드 금지 |
= 0 |
순수 가상 함수 |
| 추상 클래스 | 순수 가상 함수 포함 |
virtual ~Base() |
가상 소멸자 |
dynamic_cast |
안전한 다운캐스팅 |
다음 단계¶
10_STL_컨테이너.md에서 STL 컨테이너를 배워봅시다!