6장 상속 심화_c++

hans·2023년 8월 7일
0

c++ 먹어보자

목록 보기
6/6
post-thumbnail

목차

- 1. 가상함수
- 2. 가상함수 테이블

1.가상함수

1_1.가상함수 기본 문법

가상함수는 virtual 예약어를 앞에 붙여서 선언한 메서드를 말한다
가상함수는 기본적 으로 자기부정을 전제로 작동 한다.
자기부정 이란 파생형식에서 virtual로 정의한 메서드를 재정의하면 과거의 정의가 완전히 무시 된다

virtual 반환형식 메서드이름;

예시

virtual void PrintData();

1_2.가상함수의 호출

코드를 보며 이야기 해보자

using namespace  std;;

class MyData
{
public:
	
	virtual void PrintData()
	{
		cout << "MyData(기본형식): " << m_nData << endl;
	}

	void TestFunc()
	{
		cout << "****TestFunc****" << endl;

		PrintData();
		cout << "**********" << endl;
	}

protected:
	int m_nData = 10;
};

class MyData_Ex : public MyData
{
public:
	virtual void PrintData()
	{
		cout << "MyData(파생):" << m_nData * 2 << endl;
	}

private:

};

//사용자코드
int main(void)
{
	MyData_Ex a;
    
	a.PrintData();

	MyData& b = a;

	b.PrintData();

	a.TestFunc();

	return 0;
}

출력결과
MyData(파생):20
MyData(파생):20
**TestFunc**
MyData(파생):20

사용자 코드에서 3가지 경우에 대해 생각해 보자

1.파생 클래스 인스턴스 에서 재정의 메서드를 호출 하는 경우
첫 번쨰로 생각 해볼것은 실형식이다
virtual 메서드는 실 형식을 따르기 떄문이다
당연히 파생 형식을 따르므로 파생 클래스의 PrintData()가 호출된다.

2.참조 형식이 기본 클래스, 실 형식이 파생 클래스인 경우
역시 생각 해볼 것은 실형식이 무엇인지다
MyData& b = a 여기서 참조 형식은 MyData 지만 실형식은 MyDataEx 다
그렇기에 역시 실형식인 파생 클래스의 PrintData()가 호출된다

3.파생클래스에서 기본 클래스 메서를 호출할때 기본클래스 메서드 안에 virtual 메서드를 호출 하는 경우
a.TestFunc()를 호출하면 기본 클래스에 TestFunc()메서드를 호출한다
그러면 TestFunc() 메서드안에 Virtual PrintData()메서드를 호출하는데
이경우 호출되는 PrintData()메서드는 실 형식(파생 클래스)을 따라 정의 된 PrintData()메서드를 호출한다.
조금 더 생각해보자
기본 클래스에서 TestFunc()메서드에서 virtual메서드를 호출하는것을 시간의관점에서 생각해보면
PrintData()를 기본 클래스에서 정의 할때는 분명히 과거에 일이다
하지만 호출되는 PrintData()는 기본클래스 입장에서 미래에 일이다

가상 함수는 호출하는 것이 아니라 호출 되는 것으로 이것은 called by frameWork 라고 한다.

1_3.소멸자 가상화.

기본적으로 클래스 기본 형식에 대한 포인터를 통해 파생 클래스 인스턴스를 참조할 수 있다.

MyData *pData =  new MyData_Ex;

이렇게 파생클래스를 이용해 동적 생성한 객체를 참조할 때 메모리 누수 문제가 발생할수 있다
코드를 통해 살펴보자

class MyData
{
public:
	MyData()
	{
		m_pszData = new char[32];
	}
	
	~MyData()
	{
		cout << "기본 클래스 소멸자 호출!!" << endl;
		delete m_pszData;
	}
private:
	char* m_pszData;
};

class MyData_Ex : public MyData
{
public:
	MyData_Ex()
	{
		m_pnData = new int;
	}
	
	~MyData_Ex()
	{
		cout << "파생 클래스 소멸자 호출" << endl;
		delete m_pnData;
	}

private:
	int* m_pnData;
};

//사용자코드
int main(void)
{
	MyData* pData = new MyData_Ex;

	delete pData;

	return 0;
}

출력결과
기본 클래스 생성자 호출
파생 클래스 생성자 호출
파생 클래스 소멸자 호출

위 코드에서 사용자 코드에서 delete 연산을 실행하면 참조형식에 소멸자만 호출되고 실 형식의 소멸자가 호출되지 않는다!!.
생성자는 일반 메서드 => 일반 메서드는 참조형식을 Virtual은 실 형식을 따른다

이문제를 해결하기 위해서 소멸자 가상화를 해야한다

virtual ~MyData()
	{
		cout << "기본 클래스 소멸자 호출!!" << endl;
		delete m_pszData;
	}

출력결과
기본 클래스 생성자 호출
파생 클래스 생성자 호출
파생 클래스 소멸자 호출
기본 클래스 소멸자 호출 !!

3.순수 가상 클래스

3_1.순수 가상 클래스 특징

순수 가상 클래스는 "순수 가상 함수"를 멤버로 가진 클래스를 의미한다

virtual int GetData() const = 0;

순수 가상 함수는 선언은 미리 해두지만 정의는 미래에 하도록 미뤄둔 함수다.
순수 가상 함수는 반드시 파생 클래스 에서 재정의 해야한다.
또한 순수 가상 클래스 인스턴스를 선언하거나 동적으로 생성할수없다

//초기 개발자
class MyInterface
{
public:
	MyInterface()
	{
		cout << "MyInterface()" << endl;
	}

	virtual int GetData() const = 0;
	virtual void SetData(int nParam)  = 0;

};

//후기 개발자
class MyData :public MyInterface
{
public:
	MyData()
	{
		cout << "MyData()" << endl;

		
	}

	virtual int GetData() const
	{
		return m_nData;
	}

	virtual void SetData(int nParam)
	{
		m_nData = nParam;
	}
private:
	int m_nData = 0;
};




//사용자코드
int main(void)
{
	MyData a;
	a.SetData(5);
	cout << a.GetData() << endl;

	return 0;
}

출력결과
MyInterface()
MyData()
5

파생 클래스 인스턴스를 생성하니 호출자 순서에 의해서 기본 클래스 -> 파생 클래스 순으로 호출 된것을 볼수 있다 . 그리고 virtual 메서드는 실형식을 따르므로 실형식인 파생 클래스의 재정의된 메서드가 호출 되고 실행 된것을 확인 할 수있다

3_2.순수 가상 메서드의 활용

순수가상메서드의 특징은 어렵지 않게 정리할 수 있었다
그렇다면 왜?순수 가상 메서드를 이용하는지에 대해 이야기 해보자
예시를 들기위해 잠깐 컴퓨터와 USB에 대해 이야기 해보겠다
대부분의 컴퓨터는 USB를 인식할 수 있는 인터페이스를 지니고 있다.
왜 USB 인터페이스를 컴퓨터가 가지고 있을까?
USB 인터페이스를 만들어서 다른 기기들과에 연결을 하기 위해서 인터페이스를 구축한다.
다른 장치와 상호작용을 위해 가장 보편적인 인터페이스를 구축한다
이 논리를 객체지향 프로그래밍에 적용해 보자

//초기 개발자
class MyObject
{
public:
	MyObject()
	{

	}
	virtual ~MyObject()
	{

	}

	virtual int GetDeviceID() = 0;

protected:
	int m_nDevice;
};

//초기 개발자가 만든 함수
void PrintID( MyObject *pobj)
{
	cout << "Device ID:" << pobj->GetDeviceID() << endl;
}

//후기 개발자가 만든 클래스
class MyTv : public MyObject
{
public:
	MyTv(int nID)
	{
		m_nDevice = nID;
	}

	virtual int GetDeviceID()
	{
		cout << "MyTV::GetDeviceID()" << endl;
		return m_nDevice;
	}

};

class MyPhone : public MyObject
{
public:
	MyPhone(int nID)
	{
		m_nDevice = nID;
	}
	
	virtual int GetDeviceID()
	{
		cout << "Myphone::GetDeviceID()" << endl;
		return m_nDevice;
	}

private:

};



//사용자코드
int main(void)
{
	MyTv a(5);
	MyPhone b(10);

	::PrintID(&a);
	::PrintID(&b);

	return 0;
}

다음 코드에서 MyObject 클래스는 추상클래스 이다
추상 클래스는 그자체를 인스턴스로 만들수 없다 말그대로 추상이기 떄문이다
하지만 추상메서드를 상속받아 만든 phone,tv는 각각의 인스턴스를 만들고
인스턴스의 주소를 넘겨 각각의 PrintID()의 실인수로서 작동하게 한다
가상 함수는 추상 자료형으로 참조 하더라도 언제나 실 형식을 메서드가 호출 될수 있다

profile
방구석여포

1개의 댓글

comment-user-thumbnail
2023년 8월 7일

개발자로서 성장하는 데 큰 도움이 된 글이었습니다. 감사합니다.

답글 달기

관련 채용 정보