자료 저장소

# 객체 포인터 변수

객체의 주소값을 저장하는 포인터 변수

Person * ptr; // 객체 포인터 변수 선언
ptr = new Person(); // 포인터 변수의 객체 참조

위 코드에서 ptr은 Person객체를 가리키게 된다. 그런데 Person형 포인터는 Person 객체뿐만 아니라,
Person을 상속하는 유도클래스의 객체도 가리킬 수 있다.

class Student : public Person { ... }

Person *ptr = new Student(); // Person을 상속하는 Student 객체를 가리킨다.

class PartTimeStudent : public Student { ... }

Student *ptr = new PartTimeStudent; // Student를 상속하는 PartTimeStudent 객체를 가리킨다.

Person *ptr = new PartTimeStudent; // 기초클래스를 간접적으로 상속하는 PartTimeStudent도 가리킨다.

이를 일반화하면 C++에서 객체 포인터 변수는 선언된 객체를 직접 혹은 간접적으로 상속하는 모든 객체를
가리킬수 있다.


IS-A관계를 통해서도 논리적으로 이해가 가능하다.

- 학생은 사람이다. (사람 → 학생)
- 근로 학생은 학생이다. (학생 → 근로학생)
- 근로 학생은 사람이다. (사람 → 근로학생)

※ 사람은 학생을 가리킬수 있지만 학생은 사람을 가리킬수 없다. 이 또한 현실세계를 역행하는 코드가 된다.

■ 객체 포인터의 권한
- 포인터를 통해서 접근할 수 있는 객체 멤버의 영역
- 포인터의 자료형(객체)에 따라 가리키는 대상에 관계없이 클래스에 정의된 멤버에만 접근가능하다.


# 함수 오버라이딩(function overriding)

함수 오버라이딩은 기초 클래스의 멤버함수와 같은 함수를 유도 클래스에 정의했을때 이를 가리켜 "유도클래스의
함수가 기초클래스의 함수를 오버라이딩 했다"라고 한다. 오버라이딩 된 기초 클래스의 함수는, 오버라이딩을 한
유도클래스의 함수에 가려진다.


※ 기초클래스와 동일한 이름의 함수를 유도클래스에서 정의한다고 해서 무조건 함수오버라이딩 되는것은 아니다.
매개변수의 자료형 및 갯수가 다르면, 이는 함수오버로딩 되어, 전달되는 인자에 따라서 호출되는 함수가 결정된다.
즉, 함수 오버로딩은 상속의 관계에서도 구성이 될 수 있다.


# 가상 함수(Virtual Function)

함수를 오버라이딩하는 이유는 객체를 가리키는 포인터 변수가 현재 자신이 가리키고 있는 객체의 멤버가 호출
되도록 하기 위함이다. 만약 포인터의 자료형을 이유로 자료형의 클래스의 멤버함수가 호출된다면 이건 분명
잘못된것이다. 따라서 C++에서는 '가상함수'라는 것을 제공하고있다.
#include <iostream> 
usingnamespacestd;

class First
{
public:
virtualvoid MyFunc()
{
cout<<"FirstFunc"<<endl;
}
};

class Second: public First
{
public:
virtualvoid MyFunc()
{
cout<<"SecondFunc"<<endl;
}
};

class Third: public Second
{
public:
virtualvoid MyFunc()
{
cout<<"ThirdFunc"<<endl;
}
};

int main(void)
{
Third * tptr=new Third();
Second * sptr=tptr;
First * fptr=sptr;

fptr->MyFunc();
sptr->MyFunc();
tptr->MyFunc();
delete tptr;
return0;
}

※ 위 코드처럼 함수가 가상함수로 선언되면, 해당 함수호출시, 포인터의 자료형을 기반으로 호출대상을 결정하지
않고 포인터 변수가 실제로 가리키는 객체(Third)를 참조하여 호출의 대상을 결정한다.


■ 가상 함수의 실체

객체 안에는 실제로 멤버함수가 존재하지 않는다. 실제로 C++의 객체와 멤버함수는 함수를 공유하는 구조를 가진다.
객체가 생성되면 멤버변수는 객체 내에 존재하지만, 멤버함수는 메모리의 한 공간에 별도로 위치하고선, 이 함수가
정의된 클래스의 모든 객체가 이를 공유하는 형태를 취한다.

virtual로 선언된 가상함수를 하나이상 포함하는 클래스에 대해서는 컴파일러가 '가상함수 테이블(Virtual-Table)'을
만든다. 이는 실제로 호출되어야 할 함수의 위치정보를 담고 있는데 이를 기반으로 함수가 호출되며 오버라이딩 된
가상함수는 유도클래스의 가상 함수 테이블에 존재하지 않는다. 때문에 가장 마지막에 오버라이딩 한 유도 클래스의
멤버함수가 호출된다.


■ 순수가상함수(Pure Virtual Function)와 추상클래스(Abstract Class)
class Employee 
{
private :
char name[100];
public:
Employee(char *name) {...}
void ShowName() {...}
virtualint GetPay() const = 0; // 순수 가상함수
virtualvoid ShowInfo() const =0; // 순수 가상함수
};

'순수가상함수'란 함수의 몸체가 정의되지 않은 함수를 의미한다.이를 표현하기 위해 코드처럼 '0의 대입'을 표시
하고 있다. 이것은 0을 대입하라는 것이 아니고 명시적으로 몸체를 정의하지 않았음을 컴파일러에게 알리는것이다.
이처럼 순수가상함수를 하나이상 지니는 클래스는 객체를 생성하려 할때 컴파일 에러가 발생한다.
이는 완전하지 않은, 그래서 객체생성이 불가능한 클래스라는 의미를 지니기 때문이다.

그리고 이러한 클래스를 가리켜 '추상클래스(abstract class)'라 한다
.


# 다형성(Polymorphism)

다형성은 어떤 문법이 아니라 가상함수의 호출관계에서 보인 특성을 가리켜 '다형성'이라고 한다.
'다형성(Polymorphism)'이란 '동질이상'을 의미한다. 즉, 다음과 같은 의미를 지닌다.

 "모습은 같은데 형태는 다르다"

이를 C++에 적용하면, "문장은 같은데 결과는 다르다"로 표현할 수 있다.

가장 대표적인 예로 자료형이 같은 포인터가 상속관계에 있는 서로 다른 객체를 가리키고 가상으로 선언된 함수를
호출하면 이는 다른 실행결과가 나타난다. 따라서 호출문장을 같지만 결과는 다른 '다형성'의 예로 볼 수 있다.


# 가상소멸자(Virtual Destructor)

virtual선언은 소멸자에도 올 수 있다. 객체의 소멸시 현재 참조하고 있는 포인터로 호출 할 경우 현재 참조되고 있는
객체의 소멸자만 호출된다. 따라서 이러한 경우에 메모리 누수가 발생하게 된다.
따라서 객체의 소멸은 delete 연산자에 사용된 포인터 변수의 자료형에 상관없이 모든 소멸자가 호출되어야 한다.
그리고 이를 위해서는 소멸자에도 virtual 선언을 추가하면 된다.

virtual ~First()
{
   delete[]arr;
}

※ 가상 소멸자가 호출되면, 상속의 계층구조상 맨 아래에 존재하는 유도 클래스의 소멸자가 대신 호출되면서 기초
클래스의 소멸자가 순차적으로 호출된다.



# 참조자의 참조 가능성

참조자(Reference)를 이용하여 객체를 참조할 경우 포인터의 특성 및 가상함수의 개념도 모두 그대로 적용된다.
댓글 로드 중…

최근에 게시된 글