코드 재사용: 이미 만든 코드를 다시 쓸 수 있음.
유지보수 용이: 공통 기능은 부모 클래스에, 개별적인 차이만 자식 클래스에.
구조적 프로그래밍: 큰 프로그램을 논리적으로 구조화할 수 있음.
| 용어 | 의미 |
|---|---|
| 클래스(class) | 설계도. 객체(Object)를 만들기 위한 틀. |
| 부모 클래스 (기본 클래스, Base Class) | 상속해주는 클래스 |
| 자식 클래스 (파생 클래스, Derived Class) | 상속받는 클래스 |
class 자식클래스 : 접근지정자 부모클래스
- 자식 클래스의 생성자는 부모 클래스의 생성자를 호출할 수 있다.
- 아래 그림을 보면 Derived:Base(3, 4)가 있다.
이 코드의 의미는 Derived 생성자 호출 시, 코드를 수행하기 전에 Base(3,4)를 실행하라는 의미이다.
이러한 방식으로 기본 클래스는 기본 클래스의 생성자가 초기화하고, 파생 클래스는 파생 클래스의 생성자가 초기화할 수 있다.
#include <iostream>
#include <string>
using namespace std;
class Vehicle {
protected:
string color;
int speed;
public:
Vehicle(string c, int s) : color(c), speed(s) {}
void move() {
cout << "The vehicle is moving at " << speed << " km/h." << endl;
}
void setColor(string c) {
color = c;
}
string getColor() {
return color;
}
};
// 파생 클래스 1: 자전거
class Bicycle : public Vehicle {
private:
bool hasBasket;
public:
Bicycle(string c, int s, bool basket) : Vehicle(c, s), hasBasket(basket) {}
void ringBell() {
cout << "Bicycle bell: Ring Ring!" << endl;
}
};
// 파생 클래스 2: 트럭
class Truck : public Vehicle {
private:
int cargoCapacity;
public:
Truck(string c, int s, int capacity)
: Vehicle(c, s), cargoCapacity(capacity) {
}
void loadCargo() {
cout << "Truck loading cargo. Capacity: " << cargoCapacity << " tons."
<< endl;
}
};
코드를 유연하고 재사용성 높게 만들 수 있음
여러 종류의 객체를 같은 방식으로 다룰 수 있음
유지보수가 쉬움 (새로운 클래스를 추가해도 기존 코드 수정 거의 없음)
새로운 동물이 생길 때마다 관리해야 할 클래스가 많아지는 이유는 아래와 같습니다.
Lion, Wolf, Dog 같은 각 동물 유형을 개별 클래스로 작성해야 하기 때문이다.
이 문제를 해결하는 핵심 개념이 바로 다형성이다.
다형성이란, 기본이 되는 클래스를 만들어 함수의 인터페이스를 정의하고 실제 구현은 파생 클래스에서 담당하게 하는 기법을 말한다.
이때, 동적 바인딩을 통해 실제 호출된 객체의 타입에 따라 적절한 파생 클래스의 함수가 실행되도록 하려면 함수 앞에 virtual 키워드를 붙여야 합니다.
virtual 키워드를 붙이면 가상 함수 테이블이 생성된다.
그리고 이 가상 함수 테이블에는 아래 그림처럼 기본 클래스에서 virtual로 선언된 함수가 파생 클래스에서 재정의된 경우 그 위치가 기록되어 동적 디스패치가 가능해진다.

만약 virtual을 붙이지 않는다면, Derived1혹은 Derived2 클래스에서 func 메서드를 호출할 경우 Base의 func 메서드가 호출된다. 왜냐하면 실제 호출한 파생 클래스의 함수 정보를 알 수 없기 때문이다.
하지만 virtual을, 붙이는 경우, 가상 함수 테이블을 참조하여 실제 호출한 파생 클래스의 메서드가 호출된다.
#include <iostream>
#include <string>
using namespace std;
class Animal
{
public:
Animal() {}
virtual void bark() {};
};
class Lion : public Animal
{
public:
Lion(string word) :m_word(word) {}
void bark() { cout << "Lion" << " " << m_word << endl; }
private:
string m_word;
};
class Wolf : public Animal
{
public:
Wolf(string word) :m_word(word) {}
void bark() { cout << "Wolf" << " " << m_word << endl; }
private:
string m_word;
};
class Dog : public Animal
{
public:
Dog(string word) :m_word(word) {}
void bark() { cout << "Dog" << " " << m_word << endl; }
private:
string m_word;
};
void print(Animal* animal)
{
animal->bark();
}
int main()
{
Lion lion("ahaaaaaa!");
Wolf wolf("ohhhhh");
Dog dog("oooooooooooooops");
print(&lion);
print(&wolf);
print(&dog);
return 0;
}
#include <iostream>
using namespace std;
// 기본 클래스: Animal
class Animal {
public:
// 가상 함수: 자식 클래스에서 재정의 가능
virtual void makeSound() {
cout << "Animal makes a sound." << endl;
}
};
// 파생 클래스: Dog
class Dog : public Animal {
public:
void makeSound() {
cout << "Dog barks: Woof! Woof!" << endl;
}
};
// 파생 클래스: Cat
class Cat : public Animal {
public:
void makeSound() {
cout << "Cat meows: Meow! Meow!" << endl;
}
};
int main() {
// Animal 타입 포인터로 다양한 객체를 가리킴
Animal* myAnimal;
Dog myDog;
Cat myCat;
// Dog 객체 가리키기
myAnimal = &myDog;
myAnimal->makeSound(); // Dog의 makeSound() 호출
// Cat 객체 가리키기
myAnimal = &myCat;
myAnimal->makeSound(); // Cat의 makeSound() 호출
return 0;
}
이전 예제 코드의 Animal 클래스를 다시 보자.
Animal 클래스를 정의한 이유는, 파생 클래스의 공통된 인터페이스를 제공하여 다형성을 구현하기 위함이다.
따라서 구조상 bark()를 가상 함수로 정의하고, 자식 클래스에서 각각 구현했다.
여기서 한 가지 생각해 보자.
사자의 울음소리는 있다. 개의 울음소리도 있다.
하지만 동물이라는 개념 자체에는 특정한 울음소리가 없다.
그럼에도 불구하고, 인터페이스 제공을 위해 반드시 포함해야 한다.
이런 경우(인터페이스는 필요하지만 기본 클래스에서 구현할 필요가 없는 경우) 순수 가상 함수로 선언한다.
가상 함수를 정의하고 = 0을 붙여주면 된다.
예를 들어, virtual int memFunc() = 0; 을 하면 순수 가상 함수가 된다.
순수 가상 함수를 하나 이상 포함하는 클래스를 추상 클래스라고 한다.
추상 클래스는 직접 인스턴스를 생성할 수 없고, 포인터 혹은 참조를 통해서만 사용할 수 있다.

📌Animal이라는 추상 클래스를 만든다.
📌Animal 클래스에는 makeSound()라는 순수 가상 함수를 선언합니다.
📌Animal 클래스를 상속받는 Dog 클래스와 Cat 클래스를 만든다.
📌Dog 클래스와 Cat 클래스는 makeSound() 함수를 재정의하여 각각 알맞은 울음소리를 출력한다.
📌main 함수에서 구현 사항을 충분히 확인할 수 있도록 구현한다.
완성 시 아래와 같은 구조를 갖게 될 것이다.

#include <iostream>
using namespace std;
// 기본 클래스: Animal
class Animal {
public:
// 가상 함수: 자식 클래스에서 재정의 가능
virtual void makeSound() = 0;
};
// 파생 클래스: Dog
class Dog : public Animal {
public:
void makeSound() {
cout << "Dog barks: Woof! Woof!" << endl;
}
};
// 파생 클래스: Cat
class Cat : public Animal {
public:
void makeSound() {
cout << "Cat meows: Meow! Meow!" << endl;
}
};
int main() {
// Animal 타입 포인터로 다양한 객체를 가리킴
Animal* myAnimal;
Dog myDog;
Cat myCat;
// Dog 객체 가리키기
myAnimal = &myDog;
myAnimal->makeSound(); // Dog의 makeSound() 호출
// Cat 객체 가리키기
myAnimal = &myCat;
myAnimal->makeSound(); // Cat의 makeSound() 호출
return 0;
}
| 구분 | 설명 | 예시 |
|---|---|---|
| 일반 클래스 | 직접 객체 생성 가능 | Dog d; |
| 추상 클래스 | 순수 가상 함수 1개 이상 | class Animal { virtual void speak() = 0; }; |
| 인터페이스 | 모든 함수가 순수 가상 함수 | C++에서는 순수 가상 함수만 있는 클래스 |