개발/C++

15장 상속과 다형성

민돌이2 2021. 12. 9. 23:19

한성대학교 김설현교수님 강의내용을 바탕으로 작성함

 

상속(Inheritance)

C++에서 상속은 소프트웨어의 재사용하기 위한 강력한 기능

부모에게 무엇인가를 물려받는 것을 의미한다.

부모가 되는 클래스를 기본 클래스라하고 자식이 되는 클래스를 파생 클라스라고 한다.

 

기본 클래스

GeometricObject.h

#ifndef GEOMETRICOBJECT_H
#define GEOMETRICOBJECT_H
#include <string>
using namespace std;

class GeometricObject
{
public:
	GeometricObject();
	GeometricObject(const string& color);
	string getColor() const;
	void setColor(const string& color);
	string toString() const;

private:
	string color;
};
#endif

GeometricObject.cpp

#include "GeometricObject.h"

GeometricObject::GeometricObject() //디폴트 생성자
{ 
	color = "white";
}

GeometricObject::GeometricObject(const string& color) //생성자
{
	this->color = color;
}

string GeometricObject::getColor() const 
{
	return color;
}

void GeometricObject::setColor(const string& color) 
{
	this->color = color;
}

string GeometricObject::toString() const 
{
	return "Geometric Object";
}

 

자식 클래스

Circle.h

#ifndef CIRCLE_H
#define CIRCLE_H
#include "GeometricObject.h" //안하면 GeometricObject이 뭔지 몰라서 에러

class Circle : public GeometricObject //상속하는 부분
{
public:
	Circle();
	Circle(double radius);
	Circle(double radius, const string& color);
	double getRadius() const;
	void setRadius(double);
	string toString() const;

private:
	double radius;
};
#endif

Circle.cpp

#include "Circle.h"

Circle::Circle() //디폴트 생성자
{  
	radius = 1;
}
Circle::Circle(double radius) //일반 생성자1
{  
	this->radius = radius;
}
Circle::Circle(double radius, const string& color) //일반 생성자2
{  
	this->radius = radius;
	setColor(color);
}

double Circle::getRadius() const 
{
	return radius;
}

void Circle::setRadius(double radius) 
{
	this->radius = (radius >= 0) ? radius : 0;
}

string Circle::toString() const 
{
	return "Circle object";
}

 

protected키워드

클래스 외부로부터 데이터 필드와 함수에 접근할 수 있는지 명시하기 위해 private와 public키워드를 사용했다.

private멤버는 클래스 내부 또는 friend함수와 클래스에서만 접근이 가능했고, public멤버는 다른 클래스에서 접근이 가능하다.

파생 클래스에서 기본 클래스의 데이터 필드나 함수에 접근하도록 허용하기 위해선 protected키워드를 사용한다.

protected키워드는 파생 클래스에서만 접근이 가능하고, 파생 클래스가 아닌 경우 접근이 불가하다.

 

생성자와 소멸자

데이터 필드나 함수와 달리 기본 클래스의 생성자와 소멸자는 파생 클래스로 상속되지 않는다.

기본 클래스의 데이터 필드를 초기화하기 위해서 파생 클래스의 생성자로 부터(명시적 혹은 암시적) 호출만 가능하다.

만약 기본 생성자가 호출되지 않으면, 기본 클래스의 인수 없는 생성자가 호출된다.

 

생성자와 소멸자의 연쇄적 처리

클래스의 인스턴스를 생성할 때 상속 연결을 따라 모든 기본 클래스들의 생성자를 호출한다.

기본 클래스의 생성자는 파생 클래스의 생성자보다 먼저 호출된다(장유유서).

반대로 소멸자는 파생 클래스의 소멸자가 먼저 호출되는 역순으로 자동 호출된다.(만약 기본클래스가 먼저 삭제된다면, 파생 클래스가 삭제될 때 필요할 수도 있는 기본 클래스의 데이터가 이미 삭제되어 문제가 발생할 수 있기 때문에)

 

함수 오버라이딩

기본 클래스의 함수를 파생 클래스가 덮어씌운다고 생각하면 된다. 이때 기본 클래스와 매개 변수, 반환 값이 같아야 한다.

기본 클래스

string GeometricObject::toString() const
{
	return "Geometric Object";
}

파생 클래스

string Circle::toString() const
{
	return "Circle object";
}

함수를 오버라이딩하면 생성한 객체에 맞는 함수가 호출된다.

GeometricObject shape;
cout << shape.toString() << endl; //기본 클래스의 toString 호출

Circle circle(5);
cout <<circle.toString() << endl; //파생 클래스의 toString 호출

 

만약 오버라이딩한 함수를 파생클래스에서 기본 클래스의 함수를 사용하고 싶다면

Circle circle(5);
circle.GeometricObject::toString();

 

업캐스팅과 다운캐스팅

파생 클래스 유형의 포인터를 기본 클래스 유형의 포인터로 할당하는 것을 업캐스팅(upcatsing)이라고 한다.

기본 클래스 유형의 포인터를 파생 클래스 유형의 포인터로 할당하는 것을 다운캐스팅(downcasting)이라고 한다.

업캐스팅 : 자식 -> 부모

다운캐스팅 : 부모 -> 자식

업캐스팅은 암시적으로 수행될 수 있다.

void displayGeometricObject(const GeometricObject* g) //참조형도 가능
{ 
  	cout << g->toString() << endl; //(*g).toString()도 가능
}
int main() 
{
  	GeometricObject geometricObject;
  	displayGeometricObject(&geometricObject);

  	Circle circle(5);
  	displayGeometricObject(&circle); //업캐스팅 발생

  	Rectangle rectangle(4, 6);
  	displayGeometricObject(&rectangle); //업캐스팅 발생

 	return 0; 
}

 

하지만 다운캐스팅은 특별한 경우가 아니고는 불가능하다.

간단하게 생각해보면 자식은 부모의 것을 갖고 있기 때문에 문제가 없지만, 부모는 자식이 무엇을 갖고 있고 수행하는지 모른다.

 

가상 함수

displayGeometricObject(circle)을 실행할 때 Circle에 정의된 toString() 함수를 호출할 수 있을까?

이는 toString()을 기본 클래스 GeometricObject 내에 가상 함수(virtual funcion)로 선언함으로써 가능하게 한다.

virtual string toString() const;

가상함수는 동적결합을 사용해 호출한다.

 

정적 결합(static binding)

컴파일 시 호출될 함수가 결정되는 것이다. 선언되어 있는 타입을 보고 바인딩 하므로 실제 저장된 객체가 무엇이든 선언된 객체를 함수로 호출한다.

 

동적 결합(dynamic binding)

프로그램 실행 시 호출될 함수가 결정되므로, 실제 저장된 객체의 타입에 맞춰 함수를 호출한다.

 

함수에 대해 동적 결합이 가능하도록 하려면 두가지 조건이 필요하다.

1.함수는 기본 클래스에서 virtual로 정의되어 있어야 한다(파생 클래스에서 virtual키워드를 추가할 필요 없음).

2.객체를 참조하는 변수는 참조에 의해 전달되거나 포인터로 전달되어야 한다.

(가상함수를 사용할 시 파생 클래스의 원본을 넘겨줘야 함)

 

순수 가상함수

자식클래스가 모두 공통으로 갖고 있어야하는 기능이 있는데 이 기능이 파생 클래스마다 다른 동작을 해야 한다면 이를 명시적으로 기본 클래스에서 정의하는 것이 좋다.

이럴 때 기본 클래스에서 함수의 정의없이 선언만 해두는 것을 순수 가상 함수라고 한다.

이 함수는 정의가 없다. 하지만 자식 클래스에서 이 함수를 오버라이딩 할거니까 다형성을 사용하여 이 함수를 호출하라 라는 의미다.

class GeometricObject
{
public:
	virtual double getArea() = 0; //순수 가상 함수
    virtual double getPerimeter() = 0; //순수 가상 함수
};
#endif

 

추상 클래스(abstract class)

완전 가상함수를 하나 이상 포함한 클래스를 추상 클래스라고 한다.

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

추상 클래스의 용도는 파생 클래스가 완전 가상 함수를 반드시 구현해야 한다는 강제성을 부여하여, 여러 관련 크래스를 동일한 함수로 접근할 수 있도록 하는 것이다.

 

다형성

C++에서의 다형성이란 한 함수의 이름에 여러 의미를 연관시키는 기능이다.

한가지 함수의 이름을 동일한 조작방법으로 동작시키지만 동작 방식은 다른 것을 의미한다.

다형성, 동적결합, 가상 함수는 실제로 모두 같은 주제이다.

다형성은 동적결합을 사용하는 가상 함수로 구현하는 하나의 기법이라고 볼 수 있다.

템플릿 생각하면 이해편함

728x90