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;

	// static binding (early binding)
	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()
{
	/* Derived d;
	Base &b = d; // &를 안 붙이면 객체잘림이 이루어진다.

	b.print();*/

	Base b;
	Derived d;
	// functional.reference_wrapper 사용법(다형성 유지)
	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);
	// casting에 실패하면 nullptr이 들어간다.
	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가 호출된다.
profile
좋은 지식 나누어요

0개의 댓글

Powered by GraphCDN, the GraphQL CDN