*이 포스팅은 개인 학습을 위해 내용을 정리한 것이 목적입니다.
1. 구조체와 클래스
*인터페이스 : 서로 다른 객체를 연결하는 연결고리
-인터페이스 키워드
#define interface struct
* COM규약에서 정한 하나의 규정
이진 표준을 따라야 함
-> 이진표준을 따르기 위해서는 메모리 맵에서의 데이터 정렬이 중요함.
1) C에서의 구조체 선언
다음과 같은 구조체가 있다고 가정해보자.
struct xy { char c; int x; int y; } |
위 내용은 간단히 표현하면, 메모리에 다음과 같이 적재될 수 있다.
1byte |
2byte |
3byte |
4byte |
xy::c |
|
|
|
xy::x | |||
xy::y |
xy::c의 주소를 알 수 있다면, xy::x, xy::y의 주소는 도출 가능하다. (VC++기준). 그러나 VB나 ASP의 경우 포인터의 개념이 없으므로, 이는 배열로 이루어져야한다.
만약, 이 번지 값을 가리키는 배열이 존재한다면 구조체의 값을 획득하기 쉽다. 이를 가상테이블이라 함.
2) C++에서의 구조체 선언
C++에서는 C와 다르게 구조체에 함수 선언이 가능하다. C++의 구조체는 클래스와 매우 유사하다. 차이점이라면 구조체 기본접근지시자는 public이고, 클래스는 기본 접근지시자가 private이라는 차이가 있다.
COM을 제어하기 위해서는 데이터 멤버와 함수의 메모리 주소는 순차적으로 증가해야 한다. 그러나 함수는 데이터멤버의 메모리 주소와는 무관하여, 의미가 없다. 즉, 이런 구조체는 직접적인 인터페이스 구성이 안됨.
3) C++에서의 클래스 선언
이것 역시 구조체에서 정의된 메모리 맵과 동일하다. C++의 클래스로 정의된 부분 또한, 인터페이스로 활용할 수 없다.
*C++에서는 구조체 또한 상속, 메소드, 다형성을 지원하기에 클래스와 큰 차이가 없다. 그러나 구조체는 public을 기반으로 하기에 인터페이스를 만들 때, 구조체를 더 선호한다. (한 문장으로 줄이기 위해?)
2. 단일 상속
단일상속 클래스 계층에서는, 각 파생된 클래스 상에 존재하는 새로운 인스턴스 데이터는 단순히 기본 클래스의 레이아웃에 추가된다.
클래스 선언 |
메모리 주소 |
struct parent{ int x; int y; } |
0x0066fdec : parent::x 0x0066fdf0 : parent::y |
struct child : parent{ int xc; int yc; } |
0x0066fdec : parent::x 0x0066fdf0 : parent::y 0x0066fdf4 : parent::xc 0x0066fdf8 : parent::yc |
위와 같이 상속에서는 데이터 멤버의 연속성이 보장된다.
3. 다중 상속
C++에서는 다중 상속을 지원하지만, COM을 구현하고자 할 때에는 다중상속을 지원하지 않는다. 그러나 중첩클래스를 이용하여 다중상속을 구현할 수 있다.
4. 가상 상속
다음과 같은 클래스 상속이 있다고 할 경우, 다중상속이 허용되지 않는다.
public class Life{...} public class Mammalia : public Life{ ... } public class Bird : public Life{ ... } public class Bat : public Mammalia, Bird{ ... } |
Mammalia와 Bird는 각각 Life의 멤버를 상속받아왔다. 따라서 두 클래스의 멤버에는 Life의 데이터가 있다. 그러나 이 두 클래스로부터 다중 상속을 받을 경우, Life에서 받아온 인스턴스 데이터가 각각 복사되어 적재된다. 메모리 문제와 데이터 관리의 문제가 생김.
이를 해결하기 위해 가상 상속이라는 방법을 제공함. 기본클래스가 가상적으로 존재함을 의미.
'virtual' 키워드를 붙여서 구현.
public class Life{...} public class Mammalia : virtual Life{ ... } public class Bird : virtual Life{ ... } public class Bat : public Mammalia, Bird{ ... } |
가상 기본클래스의 배치는 다음과 같다.
*가상으로 상속되지 않는 기본 클래스를 포함하는 인스턴스가 먼저 배치됨
*비가상 기본 클래스들 중의 하나로부터 상속된 경우를 제외하고는 숨겨진 vbptr(virtual base table pointer)를 추가함.
*파생된 클래스 상에 새롭게 선언된 데이터 멤버를 배치한다.
*인스턴스의 마지막에 가상으로 상속된 기본 클래스의 단일 인스턴스를 배치함
예를 들면 아래와 같음.
struct L{ int years; } struct M : virtual L{ int mammal; } struct B : virtual L { int wing; } struct Bat : M, B{ int eat; } |
위와 같은 상속 구조에서는 다음과 같이 적재됨.
M::vbptr M::mammal B::vbptr B::wing Bat::eat L::years |
5. 순수 가상 함수와 추상 기본 클래스
*순수가상함수 : 기본클래스에서 가상함수의 속성이 정의되어 있지 않은 함수. 추후 기본클래스로부터 파생될 것으로 예상되는 함수를 연결하기 위한 공간으로써 사용됨. 기본클래스에서 0으로 초기화 해주어야 함.
함수 명 앞에 virtual을 붙인다. (기본클래스). 상속받은 클래스에서는 virtual을 붙이지 않음.
*추상기본 클래스 : 하나 이상의 순수가상함수를 가진 클래스. 여기서 파생된 클래스들은 순수 가상함수를 모두 오버라이딩 해야함.
class mammal { public: virtual void crying() = 0; void print(){ cout << "hello, world!" << endl; } }
class Cat : public mammal{ public: void crying(){ cout << "mew!" << endl; } }
class Dog : public mammal{ public: void crying(){ cout << "bowwow" << endl; } }
void main(){ mammal *p_b; mammal pb; Cat c; Dog d;
pb->crying(); //컴파일 에러. p_b->crying(); // warning! c.print(); // OK
p_b = &c; p_b->crying(); // mew출력됨 p_b = &d; p_b->crying(); //bowwow출력됨 } |
추상 기본 클래스는 인스턴스를 생성하지 않았다. 순수가상함수를 가지는 클래스형의 오브젝트는 생성할 수 없기 때문이다. 만약, 추상 기본 클래스의 오브젝트에 접근하기 위해서는 파생된 클래스의 오브젝트를 이용한다.
위와 같이 작성하면..
p_b->print();는 컴파일시 경고가 발생한다. 그러나 실행에 문제는 없다. 하지만 p_b->crying(); (여기서 p_b는 mammal)에서는 경고가 발생하지만 런타임시 해당 구문을 만나면 crash.
내용 출처 - COM Bible.
댓글