driver 파일 코드 설계는 다음과 같이 했다.
#include <iostream>
using namespace std;
#include <vector>
#include "Shape.h"
int main() {
vector < Shape * > handle; // 핸들이 될 기본 클래스 포인터 자료형의 벡터 생성
handle[0] = new Circle; // 원
handle[1] = new Square; // 사각형
handle[2] = new Sphere; // 구면체
handle[3] = new Cube; // 정육면체
for (int i = 0; i < 4; i++) // handle 내의 도형을 처리하는 루프
{
/*
* if(handle[i]의 객체가 TwoDimesionalShape클래스의 파생 클래스 객체이면) cout << "Area : " << getArea();
* else
* cout << "Area : " << getArea() << endl;
* cout << "Volume : " << getVolume() << endl;
*
*/
}
}
if문을 어떻게 구현할까 생각하다가, handle[i]는 Shape의 포인터 자료형이므로 상속 계층의 한 단계 아래로 낮춘 것이 TwoDimesional 클래스인지 , Three~인지 확인하면 될 것이라 생각하였다.
어떻게 상속 계층 구조에서 한단계 낮출 것인가? 구글링 하다가
DynamicCasting을 발견하였다,
[C++] dynamic_cast (타입캐스트 연산자) (tistory.com)
이 과정에서 동일한 vector(Shape * 자료형) 에서 하나의 요소만을 Two~혹은 Three~ 즉 파생 클래스로 바꿀 수 있는지에 대한 의문이 들었다.
https://stackoverflow.com/questions/26208918/vector-that-can-have-3-different-data-types-c
https://stackoverflow.com/questions/6274136/objects-of-different-classes-in-a-single-vector
공부하다가 든 의문점
#include <iostream>
#include <vector>
using namespace std;
class Parent {
virtual void print() {
cout << "Printing!" << endl;
}
};
class Type1 : public Parent {
void print() {
cout << "Printing1!" << endl;
}
};
class Type2 : public Parent {
void print() {
cout << "Printing2" << endl;
}
};
class Type3 : public Parent {
void print() {
cout << "Printing3" << endl;
}
};
int main() {
vector <Parent* > vec;
vec.push_back(new Type1);
vec.push_back(new Type2);
vec.push_back(new Type3);
vec[0]->print();
return 0;
}
이 코드에서 마지막 줄의 print()는 왜 Parent 즉 기반 클래스의 함수로 선언되는 것이며 접근할 수 없다고 뜨는걸까?
접근 제어 지정자 안정했자나 default가 private니까 당연히 main함수에서 접근이 안되는 거지 ^^.. 아오
가상함수
[C++] 가상함수(virtual) 사용법 총정리 (tistory.com)
가상함수는 파생 클래스에서의 재정의를 기대하고 정의해놓은 함수!
- 기반 클래스에서 작성된 가상함수는 파생 클래스에서 재정의하면 이전에 정의되었던 내용들은 모두 새롭게 정의된 내용들로 교체된다.
#include <iostream>
using namespace std;
class Parent {
public:
virtual void print() {
cout << "여기는 Parent" << endl;
}
};
class Child : public Parent {
void print() {
cout << "여기는 Child!" << endl;
}
};
int main()
{
Parent* p = new Parent;
Child* c = new Child;
p->print();
p = c;
p->print(); // 정적 바인딩으로, 부모 클래스의 함수가 호출된다.
c->print(); // 왜 접근하지 못하지?
// 접근 지정자 오류. default가 private이기 때문에 public을 써주어야 한다.
}
컴파일러는 원래 정적바인딩을 사용하여, 포인터 변수의 주소를 Child로 바꾸어주었음에도 불구하고 정적바인딩으로 인해 ( 컴파일 당시 호출된 함수의 번지가 이미 결정되었기 때문에) 런타임에서 이를 인지하지 못하고 Parent클래스의 멤버 함수를 호출하는 것이다.
virtual 함수를 기본 클래스의 자료형 앞에 넣어주면, 포인터의 타입이 아닌 이 포인터가 가르키는 객체의 타입에 따라 멤버함수를 선택하게 되는 것이다.
추가적으로, 가상 소멸자와 참조 가능성에 대해 알아보자
#include <iostream>
using namespace std;
class First {
private:
char* strOne;
public:
First(const char* str)
{
strOne = new char[strlen(str) + 1];
}
~First() {
cout << "~First()" << endl;
delete[]strOne;
}
};
class Second :public First
{
private:
char* strTwo;
public:
Second(const char* str1, const char* str2)
:First(str1)
{
strTwo = new char[strlen(str2) + 1];
}
~Second() {
cout << "~Second()" << endl;
delete[] strTwo;
}
};
int main() {
First* ptr = new Second("simple", "complex");
delete ptr;
return 0;
}
다음과 같은 전형적인 상속의 형태에서, 객체가 First 포인터 자료형이다 보니까 First 클래스의 소멸자만 호출되고 동적할당된 Second의 소멸자는 호출되지 않는 것을 확인할 수 있다. -> 메모리 누수
virtual 을 First 소멸자 앞에 선언하면, 그 First 소멸자 대신에 Second 소멸자가 호출, 상속 관계에 의해 First 소멸자도 호출되는 것이다.
참조자의 참조 가능성
C++ 에서 AAA형 포인터 변수는 AAA 객체 또는 AAA를 직접, 간접적으로 상속하는 모든 객체를 가르킬 수 있다.
이러한 특성은 참조자에도 적용이 가능하다.
C++에서 AAA형 참조자는 AAA 객체 또는 AAA를 직접, 간접적으로 상속하는 모든 객체를 참조할 수 있다.
#include <iostream>
using namespace std;
class First {
public:
void FirstFunc() {
cout << "First Func() " << endl;
}
virtual void SimpleFunc() {
cout << "First's SimpleFunc()" << endl;
}
};
class Second :public First {
public:
void SecondFunc() {
cout << "Seoncd func" << endl;
}
virtual void SimpleFunc() {
cout << "First's SimpleFunc()" << endl;
}
};
class Third :public Second {
public:
void ThirdFunc() {
cout << "Third func" << endl;
}
virtual void SimpleFunc() {
cout << "Third's SimpleFunc()" << endl;
}
};
int main()
{
Third obj;
obj.FirstFunc();
obj.SecondFunc();
obj.ThirdFunc();
obj.SimpleFunc(); // Third's SimpleFunc()
Second& sref = obj; // Second형 참조자에 obj(Third 클래스 객체) 할당
sref.FirstFunc();
sref.SecondFunc();
sref.SimpleFunc(); // Third's SimpleFunc()
First& fref = obj; // First형 참조자에 obj(Third 클래스 객체) 할당
fref.FirstFunc();
fref.SimpleFunc(); // Third's SimpleFunc()
return 0;
}
어쨌거나 다시 돌아와서,
vector 요소에서 동일한 기본 클래스에서 파생된 클래스의 객체를 할당하여 구현할 수 있다.
이는 다른 자료형으로 구분되어 컴파일 에러를 일으키지 않고, 동일한 Shape * 자료형이지만 가르키는 객체가 다르다(Cricle, Sphere..) 는 형태로 벡터가 생성된다.
"How to check which class is inheriting in c ++" 를 검색하자 뜬 stack overflow 게시물!
다이나믹 캐스팅을 해보자.
다이나믹 캐스팅(Dynamic Casting)이란? : 안전하게 다운캐스팅하기 위한 방안이다. 포인터 형식에서 캐스트를 수행할 수 없는 경우 NULL값을 반환하는 데, 이를 활용해서 if문을 처리하면 될 것 같다.
심각도 코드 설명 프로젝트 파일 줄 비표시 오류(Suppression) 상태 오류 C2683 'dynamic_cast': 'Shape'은(는) 다형 형식이 아닙니다. Project26 C:\Users\User\source\repos\Project26\Project26\Shape_main.cpp 18
handle[i]의 타입인 Shape *에 virtual 함수가 하나도 없어서란다. .. 가상함수 테이블이 생성되지 않아 Shape * 자료형을 TwoDimesional *자료형으로 바꿀 수가 없다나?
Shape 클래스에 virtual함수를 뭐 넣을까 고민하다가 그냥 Temp함수를 virtual 키워드를 붙여서 넣었다.
그리고 main함수 안에서 TwoDimensionalShape로 dynamic_casting이 성공했을 때 그 value(Circle객체가 되겠지?)를 반환한다는데, if문과 조건문 구현에서 dynamic_cast가 중복되는 일이 벌어졌다. 그래서 그냥 따로 TwoDimensionalShape객체를 생성해서 value값을 담아두었다.
정의된 도형이 아니라고 뜨네 자꾸..^^
dynamic cast에서 문제가 있는 것 같다..
아님
vector 값을 초기화할때 push_back을 사용했는데, 포인터를 push_back의 인자로 넣으면 해당 메모리 공간이 NULL이 도니는 것을 확인할 수 있다.
따라서 다음과 같이 = 연산자로 handle 벡터를 초기화 시키는 것이 바람직하다.
프로젝트 끝!!
'Major Study > Object Oriented Programming' 카테고리의 다른 글
연산자 오버로딩 / 상속 / 다형성 구현 프로젝트 해결 과정 (0) | 2021.10.08 |
---|---|
객체지향 프로그래밍2 상속, 연산자 오버로딩, 다형성, 템플릿 관련 의문점 정리 및 해결 (2) | 2021.06.15 |
C++ template 내용 정리(열혈 C++, 강의노트, 기타 자료 참고) (0) | 2021.06.15 |
객체지향 프로그래밍2 기말고사 대비 문제 wirte-up (0) | 2021.06.14 |
2021 객체지향 프로그래밍 중간고사 대비 (0) | 2021.04.17 |