객체 포인터의 참조 관계
객체 포인터 변수
Person * ptr = new Person();
A
형 포인터 변수는 A
객체 또는 A
를 직접 혹은 간접적으로 상속하는 모든 객체를 가리킬 수 있다.
class Person{};
class Student : public Person{};
class PartTimeStudent : public Student{};
Person * ptr1 = new Student(); // Person형 포인터 변수 ptr
Person * ptr2 = new PartTimeStudent(); // Person형 포인터 변수 ptr
Student * ptr3 = new PartTimeStudent(); // Student형 포인터 변수 ptr
- 위와 같이
Person
형 포인터는 Person
객체뿐만 아니라, Person
을 상속하는 파생 클래스의 객체도 가리킬 수 있다.
각 객체 포인터에 대한 참조 가능 여부
함수 오버 라이딩
- 파생 클래스와 기본 클래스에서 동일한 이름과 형태로 두 함수를 정의하는 것
- 오버 라이딩된 기본 클래스의 함수는 오버 라이딩을 한 파생 클래스의 함수에 가려진다.
오버 로딩 vs 오버 라이딩
- 오버 로딩
- 같은 클래스 내에서 같은 이름의 메서드를 사용하는 것
- 오버 라이딩
- 기본 클래스에서 정의한 메서드를 파생 클래스에서 변경하는 것
가상 함수
- C++ 컴파일러는 포인터 연산의 가능성 여부를 판단할 때 선언한 포인터의 자료형을 기준으로 판단하지, 실제 가리키는 객체의 자료형을 기준으로 판단하지 않는다.
// 컴파일 성공
// 포인터는 Base
Base * bptr = new Derived();
// 컴파일 에러
Derived * dptr = bptr;
// 컴파일 에러
// bptr 포인터는 Base이기 때문에 Derived에 있는 함수는 호출할 수 없다.
bptr -> DerivedFunc()
// 컴파일 성공
Derived * dptr = new Derived();
// 컴파일 성공
Base * bptr = dptr;
virtual
virtual
키워드는 함수에 붙일 수 있다. 부모가 자식을 가리켜 자식에서 새롭게 정의된 기능을 쓸 수 있다.
virtual
키워드는 오버 라이딩을 성립할 수 있게 해 주어 다형성을 구현하는 핵심 역할을 한다.
- 가상 함수 포인터로 인해 클래스 크기에 4Byte가 더 들어간다.(32비트 컴퓨터 기준)
memset
으로 객체를 초기화하면 가상 함수 테이블 정보까지 같이 초기화 된다.
virtual
키워드를 사용해 가상 함수로 선언되면, 해당 함수 호출 시 포인터의 자료형을 기반으로 호출 대상을 결정하지 않고, 포인터 변수가 실제로 가리키는 객체를 참조하여 호출의 대상을 결정한다.
class A
{
public:
int a;
virtual void func()
{
cout << "A" << endl;
}
};
class B : public A
{
public:
int a;
virtual void func()
{
cout << "B" << endl;
}
};
class C : public B
{
public:
int a;
virtual void func()
{
cout << "C" << endl;
}
};
int main()
{
A* a = new A;
A* b = new B;
A* c = new C;
a->func();
b->func();
c->func();
cout << sizeof(a) << " " << sizeof(b) << " " << sizeof(c) << endl; // 4,4,4
cout << sizeof(A) << " " << sizeof(B) << " " << sizeof(C) << endl; // 8,12,16
}
- 실행 결과 : A B C
- virtual 선언이 안 되어 있다면 A A A로 출력된다.
다형성
모습은 같은데 형태는 다르다 = 문장은 같은데 결과는 다르다.
- 이름이 같은 함수여도 참조하는 객체에 따라서 결과가 다르다.
순수 가상 함수
추상 클래스
- 하나 이상의 멤버 함수를 순수 가상 함수로 선언한 클래스를 추상 클래스라 한다.
- 추상 클래스는 객체 생성이 불가능한 클래스라는 의미를 지닌다.
가상 소멸자
- 맨 위에 존재하는 기본 클래스의 소멸자만
virtual
로 선언하면 이를 상속하는 파생 클래스의 소멸자들도 모두 가상 소멸자로 선언이 된다.
- 가상 소멸자가 호출되면, 상속의 계층구조상 맨 아래에 존재하는 파생 클래스의 소멸자가 대신 호출된다.
- 파생 클래스 -> 기본 클래스의 순으로 소멸자가 호출된다.
class First
{
public:
string strOne;
First(string str) : strOne(str){}
virtual ~First()
{
cout << "~First()" << endl;
}
};
class Second : public First
{
public:
string strTwo;
Second(string str1, string str2) : First(str1), strTwo(str2) {}
~Second()
{
cout << "~Second()" << endl;
}
};
First * ptr1 = new Second("simple", "complex");
delete ptr1;
Second * ptr2 = new Second("simple", "complex");
delete ptr2;
- 모두
~Second() -> ~First()
순으로 소멸자가 호출된다.
- 소멸자에
virtual
선언이 없다면 1에서는 ~First()
만 호출이 된다.