연재 완료/C++ Lang 이론

9. 다형성

라이피 (Lypi) 2018. 7. 1. 02:37
반응형

PART 2. 객체 지향 프로그래밍

  - chapter 9. 다형성


1. 다형성(Polymorphism)이란?

  - 다형성은 객체들의 타입이 다르면 똑같은 메시지가 전달되더라도 서로 다른 동작을 하는 것을 말한다.

  - 메시지를 보내는 측에서는 객체가 어떤 타입인지 알 필요가 없어야 한다.


  - 다형성은 객체 포인터나 객체 참조자를 통하여 이루어진다.

  - 객체 포인터도 기본적으로 타입이 맞는 객체만을 가리킬 수 있지만, 부모 클래스 포인터는 자식 클래스 객체를 가리킬 수 있다.

  - 이를 상향 형변환이라고 한다.

  - 상향 형변환을 하면 상속 받은 부분만을 사용할 수 있고 나머지는 사용할 수 없다. 즉 참조할 수 있는 영역의 크기가 축소된다.


  - 반대로 부모 클래스를 가리키는 포인터가 자식 클래스를 가리키도록 형변환하는 것을 하향 형변환이라고 한다.

  - 하향 형변환은 부모 클래스가 가리키는 객체가 자식 클래스일 때만 사용해야한다.  그렇지 않으면 없는 멤버를 참조하게 될 수 있다.


  - 함수의 매개변수도 부모클래스의 포인터나 참조로 작성하는 것이 유리하다.


2. 가상함수(Virtual Funtion)

  - 부모 클래스의 포인터로 자식 클래스의 객체를 가리키고 상속된 함수를 호출하면 기본적으로는 부모 클래스의 함수가 호출된다.

  - 이때 실제로 가리키는 객체에 따라 다른 함수가 호출되어야 다형성이 있다고 할 수 있다.

  - 가상함수를 사용하면 포인터가 가리키는 실제 객체에 따라 서로 다른 멤버 함수가 자동적으로 선택된다.

  - 상속된 가상 함수를 자식 클래스에서 재정의할 때는 virtual키워드를 붙이지 않아도 가상 함수로 상속된다.


  - 함수 호출문과 함수 몸체를 연결하는 것을 바인딩이라고 한다.

  - 일반 함수는 컴파일 단계에서 바인딩이 모두 완료되며 이를 정적 바인딩(static binding)이라 한다.

  - 가상 함수는 실행 중에 함수 호출문과 함수 몸체가 연결되며 이를 동적 바인딩(dynamic binding) 또는 지연 바인딩(late binding)이라 한다.

  

3. 가상 소멸자

  - 부모 클래스의 포인터로 자식 클래스의 객체를 가리키고 이를 삭제하면 부모 클래스의 소멸자만 호출되는 문제가 있다.

  - 이를 방지하기 위해서는 부모 클래스의 소멸자를 가상 함수로 선언해줘야 한다.

  - 가상 소멸자를 사용하면 위의 포인터를 삭제할 때 정상적으로 자식 클래스의 소멸자가 호출된 뒤 부모 클래스의 소멸자가 호출되게 된다.


4. 순수 가상 함수(pure virtual funtion)

  - 순수 가상 함수는 헤더만 존재하고 몸체는 없는 함수를 말한다.

  - 문법 : virtual 반환형 함수이름(매개변수리스트) = 0;

  - 순수 가상 함수를 하나라도 가지고 있는 클래스를 추상 클래스(abstract class)라고 한다.

  - 추상 클래스로는 객체를 생성할 수 없다.

  - 추상 클래스를 상속받은 자식 클래스는 반드시 순수 가상 함수를 구현해야 한다.


Header.h

#pragma once
#include <iostream>
#include <string>

using std::cout;
using std::cin;
using std::endl;
using std::string;

Animal.h

#pragma once
#include "Header.h"

class Animal
{
protected:
	int speed;

public:
	virtual void  speak() = 0;
	virtual int   walk(int here) = 0;
	
	virtual void  setSpeed(int s) = 0;
	virtual int   getSpeed() = 0;
	virtual void  printSpeed() = 0;

	virtual void  printName() = 0;

public:

	//virtual Animal();		//생성자는 재정의될 수 없으니 가상함수로 만들수도 없다.
	Animal();
	virtual ~Animal();
};

Animal.cpp

#include "Animal.h"

//어차피 오버라이딩 해야하는 함수이므로 이렇게 안에 내용이 있어도 의미가 없다.
//순수 가상으로 정의하고 내용을 만들면?
void   Animal::setSpeed(int s)
{
	speed = s;
}



Animal::Animal()
{
	speed = 0;
}

Animal::~Animal()
{
}

Dog.h

#pragma once
#include "Animal.h"

class Dog :	public Animal
{
private:
	string name;

public:
	//오버라이딩 함수
	//vitual속성은 상속되므로 재정의한 가상 함수는 vitual키워드가 안 붙어있어도 가상함수이다.
	void  speak();
	int   walk(int here);

	void  setSpeed(int s);
	int   getSpeed();
	void  printSpeed();
	
	void  printName();

	//추가한 함수
	virtual void  bite();

public:
	Dog();
	virtual ~Dog();
};

Dog.cpp

#include "Dog.h"

//클래스 외부에서는 virtual키워드를 쓸 수 없다.
void  Dog::speak()
{
	cout << "멍멍" << endl;
}

int   Dog::walk(int here)
{
	return here + speed;

}

void  Dog::setSpeed(int s)
{
	speed = s;
}
int   Dog::getSpeed()
{
	return speed;
}
void  Dog::printSpeed()
{
	cout << speed << endl;
}

void  Dog::printName()
{
	cout << name << endl;
}

void  Dog::bite()
{
	cout << "개는 물수 있다." << endl;
}


Dog::Dog()
{
	name = "개!";
	speed = 3;
}


Dog::~Dog()
{
}

Cat.h

#pragma once
#include "Animal.h"

class Cat :	public Animal
{
private:
	string name;

public:
	//오버라이딩 함수
	//vitual속성은 상속되므로 재정의한 가상 함수는 vitual키워드가 안 붙어있어도 가상함수이다.
	void  speak();
	int   walk(int here);

	void  setSpeed(int s);
	int   getSpeed();
	void  printSpeed();

	void  printName();

	//추가한 함수
	virtual void  claw();

public:
	Cat();
	virtual ~Cat();
};

Cat.cpp

#include "Cat.h"

void  Cat::speak()
{
	cout << "야옹" << endl;
}

int   Cat::walk(int here)
{
	return here + speed;
}

void  Cat::setSpeed(int s)
{
	speed = s;
}

int   Cat::getSpeed() 
{
	return speed;
}

void  Cat::printSpeed()
{
	cout << speed << endl;
}

void  Cat::printName()
{
	cout << name << endl;
}

void  Cat::claw()
{
	cout << "고양이는 할퀼 수 있다." << endl;
}

Cat::Cat()
{
	name = "고양이!";
	speed = 4;
}


Cat::~Cat()
{
}

JumpingCat.h

#pragma once
#include "Cat.h"
class JumpingCat :	public Cat
{
private:
	string name;

public:
	//오버라이딩 함수
	//부모에 vitual이 안 붙어있어도 부모의 부모에 붙은 virtual이 상속된다.
	void  speak();
	int   walk(int here);

	void  printName();

	//추가한 함수
	void jump();

public:
	JumpingCat();
	virtual ~JumpingCat();
};

JumpingCat.cpp

#include "JumpingCat.h"

//오버라이딩 함수
void  JumpingCat::speak()
{
	cout << "grrrrrrr" << endl;
}

int   JumpingCat::walk(int here)
{
	//부모의 멤버 변수를 그대로 쓸 수 있다.
	return here + speed;
}

void  JumpingCat::printName()
{
	cout << name << endl;
}

//추가한 함수
void JumpingCat::jump()
{
	cout << "JumpingCat은 점프도 할 수 있다!" << endl;
}

JumpingCat::JumpingCat()
{
	//멤버 변수를 오버라이딩 하면 자식 클래스의 멤버 변수가 부모 클래스의 멤버 변수의 이름을 완전히 가리게된다.
	name = "점핑 캣!";
	//부모의 멤버 변수를 새롭게 초기화해서 그대로 쓸 수 있다.
	speed = 5;
}


JumpingCat::~JumpingCat()
{
}

main.cpp

#include "Dog.h"
#include "JumpingCat.h"

//매개변수를 부모클래스의 참조나 포인터로 받으면 자식 클래스를 다 받을 수 있다.
void temp(Animal& ani)
{
	ani.printSpeed();
}

void temp1(Animal* ani)
{
	ani->printSpeed();
}

//추상클래스를 매개변수로 사용할 수 없다.
//void temp2(Animal ani)
//{
//	ani.printSpeed();
//}


int main()
{
	//추상 클래스로는 객체를 만들 수 없다.
	//Animal ani;

	Animal* ani[3];
	ani[0] = new Dog;
	ani[1] = new Cat;
	ani[2] = new JumpingCat;

	//부모 클래스의 포인터로 자식 클래스를 가리켜서 사용하기 
	for (int iCnt = 0; iCnt < 3; iCnt++) {
		ani[iCnt]->printName();
		ani[iCnt]->speak();
		cout << ani[iCnt]->walk(10) << endl;
		
		//참조를 받는 함수에 포인터를 쓸 수 없다.
		//temp(ani[iCnt]);

		temp1(ani[iCnt]);
	}

	//부모 클래스의 포인터로 자식 클래스의 멤버 함수를 호출할 수는 없다.
	//ani[0]->bite();
	//ani[1]->claw();
	
	//하향 형변환을 하면 가능하다.
	Dog* ani_0 = dynamic_cast<Dog*> (ani[0]);
	ani_0->bite();
	
	Cat c;
	temp(c);

	JumpingCat jc;
	temp1(&jc);
}



가상 소멸자가 필요한 이유


Header.h

 #pragma once
#include <iostream>
#include <string>

using std::cout;
using std::cin;
using std::endl;
using std::string;

DynamicParents.h

 #pragma once
#include "Header.h"

class DynamicParents
{
protected:
	char* s;

public:
	virtual void printString();

public:
	DynamicParents(const char* p);
	virtual ~DynamicParents();
};

class DynamicParents_noVirtual
{
protected:
	char* s;

public:
	virtual void printString();

public:
	DynamicParents_noVirtual(const char* p);
	~DynamicParents_noVirtual();
};

DynamicParents.cpp

 #include "DynamicParents.h"

void DynamicParents::printString()
{
	cout << s << endl;
}


DynamicParents::DynamicParents(const char* p)
{
	cout << "   Parents 생성자" << endl;
	s = new char[strlen(p) + 1];
	strcpy_s(s, strlen(p) + 1, p);
}


DynamicParents::~DynamicParents()
{
	cout << "   Parents 소멸자" << endl;
	delete[] s;
}


void DynamicParents_noVirtual::printString()
{
	cout << "   " << s << endl;
}


DynamicParents_noVirtual::DynamicParents_noVirtual(const char* p)
{
	cout << "   Parents 생성자" << endl;
	s = new char[strlen(p) + 1];
	strcpy_s(s, strlen(p) + 1, p);
}


DynamicParents_noVirtual::~DynamicParents_noVirtual()
{
	cout << "   Parents 소멸자" << endl;
	delete[] s;
}

DynamicChild.h

 #pragma once
#include "DynamicParents.h"
class DynamicChild : public DynamicParents
{
private:
	char* tag;
	
public:
	void printString();

public:
	DynamicChild(const char* t, const char* p);
	virtual ~DynamicChild();
};

class DynamicChild_noVirtual : public DynamicParents_noVirtual
{
private:
	char* tag;

public:
	void printString();

public:
	DynamicChild_noVirtual(const char* t, const char* p);
	virtual ~DynamicChild_noVirtual();
};

DynamicChild.cpp

 #include "DynamicChild.h"

void DynamicChild::printString()
{
	cout << "   " << tag << s << tag << endl;
}


DynamicChild::DynamicChild(const char* t, const char* p) : DynamicParents(p)
{
	cout << "   child 생성자" << endl;
	tag = new char[strlen(t) + 1];
	strcpy_s(tag,strlen(t)+1,t);
}


DynamicChild::~DynamicChild()
{
	cout << "   child 소멸자" << endl;
	delete[] tag;
}

void DynamicChild_noVirtual::printString()
{
	cout << "   " << tag << s << tag << endl;
}


DynamicChild_noVirtual::DynamicChild_noVirtual(const char* t, const char* p) : DynamicParents_noVirtual(p)
{
	cout << "   child 생성자" << endl;
	tag = new char[strlen(t) + 1];
	strcpy_s(tag, strlen(t) + 1, t);
}


DynamicChild_noVirtual::~DynamicChild_noVirtual()
{
	cout << "   child 소멸자" << endl;
	delete[] tag;
}

main.cpp

 #include "DynamicChild.h"

int main()
{
	{
		cout << "가상 소멸자가 없으면" << endl;

		DynamicParents_noVirtual* x = new DynamicChild_noVirtual("--", "메모리 누수 있음");

		x->printString();

		delete x;

		cout << "자식 소멸자가 호출되지 않음" << endl;
	}




	cout << endl;


	{
		cout << "가상 소멸자가 있으면" << endl;

		DynamicParents* o = new DynamicChild("--", "메모리 누수 없음");

		o->printString();

		delete o;

		cout << "부모 생성자, 자식 생성자, 자식 소멸자, 부모 소멸자 다 호출 됨" << endl;
	}
}




반응형