1. 다형성의 기본 개념
- 객체 지향 프로그래밍에서 중요한 개념 중 하나로, '많은 형태를 가짐'을 의미하는 그리스어에서 유래
- 프로그래밍 언어가 코드의 구조를 단일한 인터페이스 뒤에 다양한 타입의 객체로 처리할 수 있게 함으로써 코드의 유연성을 증가시키는 것
- dog: animal라는 상속 받은 클래스가 있을 때
animal.speak();
dog.speak();
- 만약 Animal 클래스안 speak함수 앞에 virtual을 붙이면 부모클래스의 포인터에서 자식클래스의 포인터인 것처럼 행동한다. 이러한 성질을 다형성이라고 한다.
2. override, final
- 상속 받은 클래스 내에서 override를 작성하면
void print(short x) override {cout << "B" << endl;}
- 잘못된 경우 알려준다.
- final 함수를 사용하면 override 할 수 없다.
3. 가상소멸자
- 부모 클래스와 자식클래스에 모두 소멸자가 있다면
- 부모클래스 소멸자에 virtual을 붙여야 메모리 누수를 방지할 수 있다.
4. 정적바인딩 동적바인딩
- 정적바인딩: 컴파일 시점에 함수 호출이 결정되는 프로세스, 빠르고 효율적
int main()
{
int x, y;
cin >> x >> y;
int op;
cout << "0: add, 1 : subtract, 2 : multiply" << endl;
cint >> op;
int result;
switch (op)
{
case 0: result = add(x, y); break;
case 1: result = subtract(x, y); break;
case 2: result = multiply(x, y); break;
}
cout << result << endl;
}
- 동적바인딩: 실행 시점에 어떤 함수를 호출할지 결정, 실행 시점에 다른 함수를 호출할 수 잇는 가능성을 열어줌
int main(){
int(*func_ptr)(int, int) = nullptr;
switch (op)
{
case 0: func_ptr = add; break;
case 1: func_ptr = subtract; break;
case 2: func_ptr = multiply; break;
cout << func_ptr(x, y) << endl;
return 0;
}
5. 순수가상함수, 추상 기본 클래스, 인터페이스 클래스
- 가상함수: 기본 클래스에서 선언되고 파생 클래스에서 재정의할 의도로 설계된 함수
- 순수가상함수: 선언만 있고 정의가 없는 가상 함수
class AbstractBase {
public:
virtual void pureVirtualFunc() = 0;
};
- 추상기본클래스: 하나 이상의 순수 가상함수를 포함하는 클래스, 주로 상속의 기반으로 사용
- 인터페이스 클래스: 모든 멤버 함수가 순수 가상함수인 클래스를 통해 인터페이스의 역할
class IShape {
public:
virtual void draw() = 0;
virtual void move() = 0;
};
6. 가상기본 클래스와 다이아몬드 상속
- 다이아몬드 상속문제

- 위에서 사용했던 virtual을 사용하면 해결된다. 다중 경롤를 통해 상속받은 클래스가 단 하나의 인스턴스만을 가지도록 보장한다.
7. 객체 잘림, Referenece_wrapper
- 객체잘림: 파생클래스의 객체를 기본클래스 타입의 객체에 할당할 때 발생하는 현상
- 파생 클래스의 객체가 기본 클래스 타입으로 "잘려서"파생 클래스에 있던 추가적인 멤버 변수나 함수가 손실
- std::reference_wrapper: 참조를 값처럼 복사할 수 있도록 만드는 래퍼
- 사용예시
#include <functional>
class Base
{
public:
int m_i = 0;
virtual void print()
{
cout << "I'm Base" << endl;
}
};
class Derived : public Base
{
public:
int m_j = 1;
virtual void print() override
{
cout << "I'm derived" << endl;
}
};
void doSomething(Base & b)
{
b.print();
}
int main()
{
Base b;
Derived d;
std::vector<std::reference_wrapper<Base>> my_vec;
my_vec.push_back(b);
my_vec.push_back(d);
for(auto & ele : my_vec){
ele.get().print();
}
return 0;
}
8. 동적 형변환
- 동적 형변환: 런타임에 객체의 타입을 완전하게 확인하고 변환하는 메커니즘
- dynamic_cast연산자: 주로 가상 함수를 사용하는 클래스 계층에서 다운캐스팅을 할 때 사용
- 타입 안전: 변환하려는 타입이 실제 객체의 타입과 호환되지 않으면 nullptr 반환
- 런타입체크: 객체의 실제타입을 확인
- 다형성에 사용: 다형성을 가진 객체에만 사용할 수 있다. 즉, 최소한 하나의 가상 함수를 가진 클래스 타입간에만 사용될 수 있다.
class Base
{
public:
int m_i = 0;
virtual void print()
{
cout << "I'm Base" << endl;
}
};
class Derived1 : public Base
{
public:
int m_j = 1024;
virtual void print() override
{
cout << "I'm derived" << endl;
}
};
class Derived2 : public Base
{
public:
string m_name = "Dr. Two";
virtual void print() override
{
cout << "I'm derived" << endl;
}
};
int main()
{
Derived1 d1;
Base *base = &d1;
auto *base_to_d1 = dynamic_cast<Derived1*>(base);
return 0;
}
9. 유도클래스에 출력연산자 사용하기
class Base
{
public:
Base() {}
friend std::ostream& operator << (std::ostream &out, const Base &b)
{
return b.print(out);
}
virtual std::ostream& print(std::ostream& out) const
{
out << "Base";
return out;
}
};
class Derived : public Base
{
public:
Derived() {}
virtual std::ostream& print(std::ostream& out) const override
{
out << "Derived";
return out;
}
};
int main()
{
Base b;
std::cout << b << '\n';
Derived d;
std::cout << d << '\n';
Base &bref = d;
std::cout << bref << '\n';
return 0;
}
Base
Derived
Derived
- bref가 객체 Derived에 대한 참조이므로 다형성에 의해 Derived::print가 호출된다.