💻 개인공부 💻/C | C++

[C++] 상속(Inheritance) - 2 (feat. 가상 함수와 추상클래스)

공대생 배기웅 2020. 6. 20. 02:32
반응형

출처 => C++ How to Program (Detitel / Prenticehall) / 현재 그리고 미래지향적인 C++ 프로그래밍(장석우, 임정목 / 비앤씨에듀케이션)

https://ehclub.co.kr/2135

https://private.tistory.com/25

오버라이딩

▶ 상속관계에 있는 클래스를 작성하다 보면 다음과 같이 함수가 중복되는 경우를 볼 수 있다. 

#include<iostream>
using namespace std;

class Base {
public:
	void f() {
		cout << "Base:f() called" << endl;
	}
};

class Derived :public Base {
public:
	void f() {
		cout << "Derived::f() called" << endl;
//f라는 함수 이름 중복
	}
};

void main() {
	Derived d,* pDer;
	pDer = &d;
	pDer->f();//Derived클래스의 f() 호출

	Base* pBase;
	pBase = pDer;
	pBase->f();//Base 클래스의 f() 호출
}

▶ 이렇게 함수의 이름이 중복되는 것을 함수 오버라이딩(function overriding)이라고 부른다.

 

▶ 오버라이딩이 있고, 또 오버로딩도 존재를 한다. 이 둘의 차이를 비교해보자

오버로딩과 오버라이딩 비교

 

1. 오버로딩 (Overloading) : 같은 이름의 메서드 여러 개를 가지면서 매개 변수의 유형과 개수가 다르도록 하는 기술

#include<iostream>
using namespace std;

class Overloading {
public:
	void overload() {
		cout << "Hello" << endl;
	}
	void overload(int a) {
		cout << a << endl;
	}
	void overload(string b) {
		cout << b << endl;
	}
};

void main() {
	Overloading ol;

	ol.overload();
	ol.overload(2);
	ol.overload("This is overloading");
}

▶ Overloading 클래스의 3개의 overload 메소드 다 이름이 같으나 매개변수나 모습이 다르다. 이를 오버로딩(Overloading)이라고 부른다.

 

 

2. 오버라이딩(Overridding) : 상위 클래스가 가지고 있는 메소드를 하위 클래스가 재정의해서 사용하는 기술

#include<iostream>
using namespace std;

class Parent {
public:
	void overriding(int a) {
		cout <<"Parent 클래스의 함수"<< a << endl;
	}
};

class Child : public Parent {
public:
	void overriding(int a) {
		cout <<"Child 클래스의 함수"<< a * a << endl;
	}
};

void main() {
	Parent p;
	Child c;
	p.overriding(1);
	c.overriding(2);
}

▶상위 클래스의 함수와 하위 클래스의 함수는 이름, 매개변수의 유형, 개수, 그리고 함수의 모습까지 다 같다. 상위 클래스 메소드를 같은 형태로 재정의하는 것이 바로 오버라이딩(Overriding)이다.

 

 

가상 함수의 호출

#include<iostream>
using namespace std;

class Base {
public:
	virtual void f() {//가상함수 선언
		cout << "Base::f() called" << endl;
	}
};

class Derived :public Base {
public:
	virtual void f() {
		cout << "Derived::f() called" << endl;
	}
};

void main() {
	Derived d, * pDer;
	pDer = &d;
	pDer->f();

	Base* pBase;
	pBase = pDer;
	pBase->f();
}

▶ 가상함수로 선언함으로서 Base클래스의 virtual void f( ) 메서드가 존재감을 상실하였다. 그러 인해 동적 바인딩이 일어나 하위 클래스인 Derived 클래스의 f( ) 메서드가 실행되었다. 

 

▶ 가상의 사전적인 의미는 "실제로 존재하지 않는"이다. 이 의미는 C++에서 함수의 호출과 클래스의 참조가 컴파일 시간에 정확히 알려지지 않는다는 의미이다.

바인딩이란 함수나 변수의 주소가 결정되는 것을 말한다. 컴파일 시간에 바인딩이 되면 이른 바인딩(early binding), 실행 시간에 바인딩이 되면 늦은 바인딩(late binding)이라고 한다. virtual이 붙으면 모두 늦은 바인딩이 된다. 

 

동적 바인딩

동적 바인딩은 파생 클래스에 대해 기본 클래스에 대한 포인터로 가상 함수를 호출하는 경우, 객체 내에 오버라이딩한 파생클래스의 함수를 찾아 실행하는 것을 말한다. 

 

▶ 동적 바인딩이 일어나는 예제

#include<iostream>
using namespace std;

class Shape {
public:
	void paint() {
		draw();
	}
	virtual void draw() {
		cout << "Shape::draw() called" << endl;
	}
};

class Circle :public Shape {
public:
	virtual void draw() {
		cout << "Circle::draw() called" << endl;
	}
//기본 클래스에서 파생클래스의 함수를 호출
};

void main() {
	Shape* pShape = new Circle();//업캐스팅
	pShape->paint();
	delete pShape;
}

▶ main()메소드를 보자

void main() {
	Shape* pShape = new Circle();//업캐스팅
	pShape->paint();
	delete pShape;
}

▶ 업 캐스팅을 하여 Shape의 객체를 Circle에서도 사용할 수 있게 하였다. 

pShape->paint()라고 하였으니 Shape 클래스의 paint()를 실행하러 간다.

▶ 하지만 draw()함수의 동적 바인딩이 발생하였다. 따라서 기본 클래스인 Shape에서 파생 클래스인 Circle 클래스의 함수인 draw()를 실행할 수 있게 된 것이다.

 

상속의 반복으로 인한 가상 함수 호출

#include<iostream>
using namespace std;

class Base {
public:
	virtual void f() {
		cout << "Base::f() called" << endl;
	}
};

class Derived :public Base {
public:
	void f() {
		cout << "Derived::f() called" << endl;
	}
};

class GrandDerived :public Derived {
public:
	void f() {
		cout << "GrandDerived::f() called" << endl;
	}
//기본 클래스 쪽에서 가상 함수를 선언하며 
//그것으로부터 파생된 클래스 내의 멤버 함수도 자동으로 가상함수가 되기 때문에
//재선언할 필요는 없다.
};

void main() {
	GrandDerived g;
	Base* bp;
	Derived* dp;
	GrandDerived* gp;

	bp = dp = gp = &g;

	bp->f();
	dp->f();
	gp->f();
}

▶ 이렇게 나온 이유는 동적 바인딩에 의해 모두 GrandDerived의 함수 f( )로 호출이 되었기 때문이다.

 

 

▶ 만약 기본 클래스의 가상 함수를 호출하고 싶다면 범위 지정 연산자(::)를 이용하여 호출할 수 있다.

#include<iostream>
using namespace std;

class Shape {
public:
	virtual void draw() {
		cout << "--Shape--";
	}
};

class Circle :public Shape {
public:
	virtual void draw() {
		Shape::draw();
		cout << "Circle" << endl;
	}
};

void main() {
	Circle c;
	Shape* pShape = &c;

	pShape->draw();
	pShape->Shape::draw();
}

▶ 직접적으로 Shape클래스의 draw( )메소드를 출력해달라고 명령하였으므로 (정적 바인딩) 위의 결과와 같이 나오게 된다. 

 

가상 소멸자

▶ 소멸자를 virtual 키워드로 선언한다. 소멸자를 호출 시 동적 바인딩이 발생한다. 

#include<iostream>
using namespace std;

class Base {
public:
	virtual ~Base() {
		cout << "~Base()" << endl;
	}
};

class Derived :public Base {
public:
	virtual~Derived() {
		cout << "~Derived" << endl;
	}
};

void main() {
	Derived* dp = new Derived();
	Base* bp = new Derived();

	delete dp;
	delete bp;
}

 

추상클래스란 무엇인가?

▶ 추상 클래스는 다른 형식의 기반 클래스로만 사용할 수 있고 개체를 생성할 수 없는 클래스를 말한다. 이와 반대되는 개념이 개체를 생성할 수 있는 클래스인 구상 클래스이다. C++에서는 멤버 메서드 중 하나라도 순수 가상 메서드를 가지고 있는 클래스를 추상클래스라고 한다. 순수 가상 메서드는 기능을 구현하지 않고 약속만 하는 메서드이다. 순수 가상 메서드는 virtual 키워드로 메서드를 선언하고 메서드 내부를 정의하지 않겠다는 의미로 '=0;을 표시'한다. 

#include<iostream>
using namespace std;

class Virtual {
public:
	virtual void f() = 0;
};

void main() {
	Virtual v//오류
	v.f();
}

▶ 오류 발생! 추상 클래스의 객체는 생성이 불가능하다.

 

 

문제

▶ 다음 코드와 실행결과를 참고하여 추상 클래스 Calculator를 상속받는 Adder와 Subractor 클래스를 구현하여라

#include<iostream>
using namespace std;

class Calculator {
	void input() {
		cout << "정수 2개를 입력하세요";
		cin >> a >> b;
	}
protected:
	int a, b;
	virtual int calc(int a, int b) = 0;
public:
	void run() {
		input();
		cout << "계산된 값은" << calc(a, b) << endl;
	}
};

void main() {
	Adder adder;
	Subtractor subtractor;
	adder.run();
	subtractor.run();
}

 

▶ 정답

class Adder :public Calculator {
protected:
	int calc(int a, int b) {
		return a + b;
	}
};

class Subtractor :public Calculator {
protected:
	int calc(int a, int b) {
		return a - b;
	}
};
728x90
반응형