본문 바로가기

Programming/MFC

파생클래스에 관한것과 클래스구성원에관한포인터

13.5 파생형에 대한 포인터

출처 . | 옹이
원문 http://blog.naver.com/mnnclup/80017887012


 

일반적으로, 하나의 형에 대한 포인터는 다른 형의 객체를 가리킬 수 없다. 그러나, 파생 클래스에 대해서만 이 규칙이 제외된다. B와 D라는 두 개의 클래스가 있다고 가정하자. 게다가, D는 기본 클래스 B로부터 파생되었다고 가정하자. 이러한 상황에서, B*형의 포인터는 D형의 객체를 가리킬 수 있다. 더 일반적으로, 기본 클래스의 포인터는, 그 기본 클래스에서 파생된 클래스의 객체를 가리키는 포인터로 사용될 수 있다.

 

비록, 기본 클래스 포인터가 파생 객체를 가리키는데 사용될 수 있지만, 그 반대는 허용되지 않는다. 즉, D*는 B형의 객체를 가리킬 수 없다. 게다가, 비록 기본 포인터를 파생 객체를 가리키는 데에 사용할 수는 있지만, 기본에서 상속된 파생 형의 구성원에 대해서만 접근이 가능하다. 즉, 파생 클래스에 의해 추가된 어떠한 구성원으로도 접근할 수 없다. (그러나, 파생 포인터 내에 기본 포인터를 캐스트 하여 전체 파생 클래스에 대해 전체적으로 접근을 할 수 있다.)

 

다음은 기본 포인터로 파생 객체에 접근하는 짧은 프로그램이다.

 

#include <iostream>

using namespace std;

class base {

int i;

public:

void set_i(int num) { i=num; }

int get_i() { return i; }

};

 

class derived: public base {

int j;

public:

void set_j(int num) { j=num; }

int get_j() { return j; }

};

 

int main()

{

base *bp;

derived d;

bp = &d; // base pointer points to derived object

// access derived object using base pointer

bp->set_i(10);

cout << bp->get_i() << " ";

/* The following won't work. You can't access elements of

a derived class using a base class pointer.

bp->set_j(88); // error

cout << bp->get_j(); // error

*/

return 0;

}

 

보다시피, 기본 포인터는 파생 클래스의 객체에 접근하기 위해 사용되었다.

 

그렇지만, 기본 포인터를 통해 파생 클래스의 구성원에 접근하기 위해, 파생된 형의 포인터 안에 기본 포인터를 캐스트 할 수도 있으므로 조심해야 한다. 예를 들어, 다음은 올바른 C++ 코드이다.

 

// access now allowed because of cast

((derived *)bp)->set_j(88);

cout << ((derived *)bp)->get_j();

 

포인터 연산은 포인터의 기본 형과 연관된다는 것을 기억하는 것이 중요하다. 이런 이유에서, 기본 포인터가 파생 객체를 가리킬 때는, 포인터를 증가시키는 것이 파생 형의 다음 객체를 가리키게 하지는 않는다. 대신, 그것이 기본 형의 다음 객체라고 생각하는 그 무엇인가를 가리킬 것이다. 물론 이것은 항상 문제를 야기시킨다. 예를 들어, 다음의 프로그램은 구문적으로는 올바르지만, 이러한 에러를 가지고 있다.

 

// This program contains an error.

#include <iostream>

using namespace std;

class base {

int i;

public:

void set_i(int num) { i=num; }

int get_i() { return i; }

};

 

class derived: public base {

int j;

public:

void set_j(int num) {j=num;}

int get_j() {return j;}

};

 

int main()

{

base *bp;

derived d[2];

bp = d;

d[0].set_i(1);

d[1].set_i(2);

cout << bp->get_i() << " ";

bp++; // relative to base, not derived

cout << bp->get_i(); // garbage value displayed

return 0;

}

 

파생 형으로의 기본 포인터의 사용은 가상함수(virtual functions)(17장 참조)의 개념을 통한 실시간 다형성을 생성할 때 가장 유용하다.

 

13.6 클래스 구성원에 대한 포인터

 

C++에서는 포인터가 객체 구성원의 특정 인자에 대해서가 아니라, 클래스 구성원을 가리키게 하는 특별한 형의 포인터를 생성할 수 있다. 이러한 포인터를 ‘클래스 구성원에 대한 포인터(pointer to a class member)’ 또는 ‘구성원에 대한 포인터(pointer-to-member)’라고 부른다. 구성원에 대한 포인터는 보통의 C++ 포인터와는 다르다. 대신, 구성원에 대한 포인터는, 그 구성원이 존재하는 클래스의 객체 내에 오프세트(offset)만을 제공한다. 멤버포인터들은 실제 포인터가 아니므로. (도트)나 ->(화살표)는 적용되지 않는다. 포인터가 가리키는 클래스의 구성원에 접근할 때는, 구성원에 대한 포인터 연산자 .*과 ->*을 사용한다. 이것들로 구성원에 대한 포인터가 있는 클래스 구성원에 접근한다.

아래에 예시가 있다.

 

#include <iostream>

using namespace std;

class cl {

public:

cl(int i) { val=i; }

int val;

int double_val() { return val+val; }

};

 

int main()

{

int cl::*data; // data member pointer

int (cl::*func)(); // function member pointer

cl ob1(1), ob2(2); // create objects

data = &cl::val; // get offset of val

func = &cl::double_val; // get offset of double_val()

cout << "Here are values: ";

cout << ob1.*data << " " << ob2.*data << "\n";

cout << "Here they are doubled: ";

cout << (ob1.*func)() << " ";

cout << (ob2.*func)() << "\n";

return 0;

}

 

위 프로그램의, main()에서는 두 개의 멤버포인터, data와 func를 생성한다. 각각의 선언 구문에 주의를 기울이자. 구성원에 포인터를 선언할 때는, 클래스를 명기하고 범위지정연산자를 사용한다. 이 프로그램은 또한 c1의 객체, ob1과 ob2를 생성한다. 프로그램에서 보여주듯이, 멤버포인터는 함수나 자료를 가리킨다. 그 다음, 프로그램은 val과 double_val()에 대한 주소들을 가진다. 이러한 “주소들”은 val과 double_val()이 위치하는 곳에서 c1형의 객체 안으로 단지 오프세트 할 뿐이다. 마지막으로, 프로그램은 func와 double_val() 함수를 호출한다. 올바르게 .*연산자를 작동시키기 위해서 괄호가 추가적으로 필요하다.

 

객체나 참조(이 장의 뒤에서 다룬다.)를 사용하여 객체의 구성원에 접근하려면, .*연산자를 사용하여야 한다. 그러나, 객체에 대해 포인터를 사용한다면, 앞서의 프로그램에 대한 아래의 버전과 같이 ->*연산자를 사용하여야 한다.

 

#include <iostream>

using namespace std;

class cl {

public:

cl(int i) { val=i; }

int val;

int double_val() { return val+val; }

};

 

int main()

{

int cl::*data; // data member pointer

int (cl::*func)(); // function member pointer

cl ob1(1), ob2(2); // create objects

cl *p1, *p2;

p1 = &ob1; // access objects through a pointer

p2 = &ob2;

data = &cl::val; // get offset of val

func = &cl::double_val; // get offset of double_val()

cout << "Here are values: ";

cout << p1->*data << " " << p2->*data << "\n";

cout << "Here they are doubled: ";

cout << (p1->*func)() << " ";

cout << (p2->*func)() << "\n";

return 0;

}

 

이 버전에서, p1과 p2는 c1형의 객체에 대한 포인터이다. 그러므로, val과 double_val()에 접근하기 위해 ->*연산자가 사용되었다.

 

기억할 것은, 구성원에 대한 포인터들은 객체 요소들의 특정 인자들에 대한 포인터와는 다르다는 것이다. 다음의 구문을 보도록 하자. (c1은 앞 프로그램에서와 같이 선언되었다고 가정하자.)

 

int cl::*d;

int *p;

cl o;

p = &o.val // this is address of a specific val

d = &cl::val // this is offset of generic val

 

 

여기서, p는 특정 객체 내부의 정수에 대한 포인터이다. 그러나, d는 단순히 c1형 객체들에 존재할 val의 위치를 나타내는 오프세트일 뿐이다.

 

일반적으로, 구성원에 대한 포인터 연산자들은 특별한 경우에 적용된다. 전형적인 프로그래밍에서는 사용되지 않는다.