//---------------------------------------------------------------------------
// 김태성의 C++빌더 강좌 : 구조체와 클래스의 구분 사용과 중요성.
//---------------------------------------------------------------------------
C에서 C++로의 언어적 진화 속에서 가장 큰 변화는 OOP를 위한 클래스가 지원된다는 점이다. C++에서는 모든 구조체와 클래스가 동일한 클래스라는 개념으로 처리된다. 즉 컴파일러 입장에서는 struct, class 가 모두 같은 개념이라는 뜻이다. 다만 default 영역 지정자가 struct 는 pubic 이고 class는 private이라는 사실이 다르다. (union은 C++에서도 C 때처럼 계속 저장소로 남아 있으며, union 자체는 상속될 수도, 상속 받을 수도 없게 되어 있다.)
그러므로 더 이상 C 스타일의 typedef을 쓰는
typedef struct tagMyData
{
;
} MyData;
와 같은 선언문은 C++에서는 군더더기 같은 것이다. 간단히
struct MyData
{
;
};
로 충분하다. 위 두 개는 MyData라는 구조체를 선언하는 같은 것이다.
C++에서는 typedef은 오직 형의 명칭을 재정의 하는데 쓰는 것이 바람직하다.
그러므로 더 이상 구조체나 클래스는 C++컴파일러 입장에서는 구분하지 않는다.
모든 프로그램의 클래스를 struct 문장으로 정의해서 사용해도 아무런 문제가 없다.
심지어 다음과 같은 식으로 struct를 상속받은 클래스도 아무 문제없이 동작한다.
struct A
{
public:
int a;
};
class B : public A
{
public:
int b;
};
하지만, 우주가 음양으로 구성되어 있고, 사람도 보이는 육체와 보이지 않는 영체가 한 몸으로구성되어 있듯이, 모든 프로그램은 데이터와 명령코드로 구성되어 있으며, 프로그래밍에 필요한 음양의 2대 요소는 곧 데이터와 기능코드이므로, 이러한 근본정신에 의거하여 데이터와 기능코드를 구분하는 것이 여러모로 프로그래밍하기에 좋다.
데이터는 곧 int, bool, String ... 등의 여러 가지 자료형의 집합이며, 기능코드는 주로 함수로 이루어져 있다. C++의 OOP에서는 데이터라고 해서 순수 데이터만 있는 것도 아니며, 기능코드라고 해서 함수로만 이루어진 것도 아니다. 데이터이지만 기능코드를 포함할수 있고, 기능코드라도 데이터를 포함할 수 있다. 문제는 어느 것을 위주로 하느냐이다. 즉 데이터가 주장이 되는 경우와 기능이 주장이 되는 경우를 나누어 생각할 수 있이며, 이를 나누어 다루는 것이 프로그래밍의 명확성에 좋다.
데이터는 주로 구조체로 다루고, 기능은 주로 클래스로 다룬다.
여기서 구조체 struct 와 클래스 class의 구분이 생긴다.
이러한 구조체와 클래스를 구분하는 것은 프로그램의 명확성을 높이고 보다 쉬운 프로그래밍을 하는데 도움이 되는 좋은 프로그래밍 습관의 하나이다. 주요 데이터 선언은 구조체에 있고 주요 기능 구현은 클래스에 있다는 것은, 그렇지 않은 것에 비해 프로그램의 구조가 분명해진다.
다시 한번 정리하면 구조체라는 표현은 데이터가 위주가 된 struct나 union에 대한 것이며, 클래스라는 표현은 기능이 위주가 된 class 키워드로 표현된 것을 말한다. 물론 전술했다시피 C++ 컴파일러에게는 양자가 다 같으며 컴파일상의 차이가 없는 것이나 마찬가지다. 하지만 프로그래머에게 있어 이 양자의 구분은 매우 중요하며 뚜렷하게 하는 것이 좋은 프로그램을 만드는 비결 중에 하나이다.
델파이에서 구조체나 클래스는 명칭에 있어서는 구분하지 않으며, 보통 T로 시작하는 명칭을 붙인다. (물론 순수 구조체를 위한 record 키워드를 지원하지만 C++처럼 class가 역할을 대신해도 문제가 없다)
TList, TStrings, TPoint, TRect 등...
VC++에서도 보통은 구조체나 클래스를 잘 구분하지 않으며 보통 C로 시작하는 명칭을 쓴다.
CList, CString, CPoint, CRect, CArray 등... 하지만 보통 구조체는 C를 접두어로 사용하지 않은 프로그래머 임의의 명칭을 붙이며, 클래스는 통상 C 접두어를 붙인다.
C++빌더는 C++이므로 VC++처럼 하거나 델파이처럼 하거나, 마음대로 처리할 수 있다.
하지만 필자는 구조체와 클래스를 대략 구분하는 것이 좋음을 설명했으며, 이를 위해 구조체는 델파이처럼 T Prefix(접두어)를 붙이고, 클래스는 C Prefix(접두어)를 붙이기를 권한다.
이는 필자가 오랜동안 실무를 해오면서 쌓인 경험에 의해 양자를 이렇게 구분하는 것이 가장 편리하고 프로그래밍에 명확성을 더한다는 사실을 깨달았기 때문이다.
물론 이미 컴파일러가 제공하고 있거나 3자 라이브러리(3rd party library)를 사용할 때는 이미 정해 놓은 것을 따라야겠지만, 자신이 새로 만든다면 필자가 말하는 규칙을 적용하는 것이 편리하는다는 것을 느낄 것이다.
구조체는 T 접두어로 시작하고 데이터 위주이며,
클래스는 C 접두어로 시작하고 기능 위주이다.
또한 구조체는 주로 원초적인 데이터를 묶어서 정의할 때 주로 사용하며,
클래스는 복잡한 기능을 요하거나 복잡한 데이터를 묶어서 처리할 때 사용하기도 한다.
T 접두어는 원래 델파이에서 Type 에 대한 약자로 쓴데서 유래한다. 델파이의 Type은 본래는 C의 struct와 비슷했으나 C++에서는 구조체나 클래스와 같아졌다.
C++빌더는 델파이와 같은 VCL을 사용하므로, 대부분의 클래스가 T로 시작하므로 명칭의 중복을 피하고, VC++과의 호환성을 높이기 위해서라도 C 접두어를 붙이는 것이 좋다.
구조체는 주로 헤더파일에 선언하고,
클래스도 주로 헤더파일에 선언한다.
하지만 클래스의 경우는 주로 독립적인 유닛(unit) 파일로, 즉 하나의 cpp 소스 파일로 저장하는 경우가 많으며, 이때 파일명도 C로 시작하는 클래스명을 그대로 쓰는 것이 좋다.
구조체는 클래스 종속적인 경우는 클래스의 헤더파일에 선언하고, 독립적인 경우는 별도의 헤더파일로 빼내서 선언하는 것이 좋다.
클래스는 주로 헤더파일에 선언하지만, 구현은 cpp 소스파일에 하는 것이 통상적이다. 하지만 템플릿이거나 클래스가 간단한 경우는 그냥 헤더파일에 전부 구현하는 경우도 종종 있다.
구조체라 할지라도 구조체의 데이터를 참조하여 값을 판단하거나 재계산하는 등의 기능은 구조체의 멤버함수로 바로 구현하는 것이 바람직하다. 그래야 프로그래밍의 직관성을 높이고 버그를 피할 수 있으며, 프로그래머가 인식하는데 유리하다.
참고로, STL은 주로 구조체 다루기를 위주로 하며, 동적배열 역시 구조체를 다룰 때 주로 쓴다. 물론 클래스를 다루는데도 문제는 없다.
지금까지의 설명은 보다 좋은 프로그래밍을 하기 위한 필자의 설명과 권장사항이며, 어떻게 할까는 각자의 몫이다.
다음은 간단한 예로, 게임의 화면의 좌표와 히트여부를 다루는 클래스다.
CHit.h 헤더파일
//---------------------------------------------------------------------------
// 블럭 데이타
struct TBlock
{
int x;
int y;
int width;
int height;
TBlock()
{
// 보통 구조체는 생성자에서 0으로 초기화 해도 좋은 데이터로 구성하는 경우가 많다.
ZeroMemory(this, sizeof(*this));
}
};
// 동적 배열
typedef vector<TBlock> TBlocks;
// 히트 클래스
class CHit
{
public:
TBlocks Blocks;
int BlockPtr;
public:
CHit(TBlock& my, TBlock& other);
void AddBlock(TBlock& other);
bool IsHit();
bool DeleteHit();
};
CHit.cpp 소스파일
//---------------------------------------------------------------------------
CHit::CHit(TBlock& my, TBlock& other)
{
;
}
bool CHit::IsHit()
{
;
return true;
}
//---------------------------------------------------------------------------
실제 내용 구현은 하지 않았지만, 어떤 식으로 실제 적용이 이루어지는지 알수 있을 것이다.
위에서 CHit 클래스의 정의에 있어 데이터가 들어가는 부분과 코드가 들어가는 부분이 같은 public: 임에도 불구하고 public 영역 지정자를 써서 별도로 표시했는데 이렇게 나누거나, 관련있는 데이터와 멤버함수를 한번에 묶는 것이 좋다.
작은 클래스를 제외하고, 특별한 이유가 없다면 위처럼 클래스별로 소스를 유지하는 것이 좋다.
참고: 선언과 정의와 구현의 차이점.
정의는 구조체/클래스 등의 원형을 코딩하는 것이며,
선언는 실제 그 내용을 사용하기 위한 코딩이며, 실제 컴파일된 결과에 인스턴스인 데이터나 코드가 자리잡게 하는 것을 의미한다.
구현은 세부내용을 정해서 일정한 기능을 코딩하는 것이다. 물론 구현은 전체 클래스를 작성하는 것을 의미하기도 한다. 구현은 선언과 정의를 포함하기도 한다.
// 구조체 정의
struct TData
{
int x, y;
int xx, yy;
int Width();
}
// 구조체 선언
TData CoordData;
// 함수 구현
int TData::Width()
{
// 기본데이타에서 계산할 수 있는 값은 따로 변수를 설정해 값을 유지하기 보다는 함수로 값을 얻는 것이 좋다.
// 이는 데이터베이스의 기본인 정규화에 해당한다.
return xx - x;
}
//---------------------------------------------------------------------------
// 김태성 cppbuilder@naver.com
http://blog.naver.com/cppbuilder
// 이 강좌는 아무 곳에나 비상업적인 용도에 한해 내용 변경 없이 원래의 출처를 명시하여
// 배포할 수 있습니다. 단, 필자의 이메일로 게제 사실을 통보해야 합니다.
//---------------------------------------------------------------------------
{
...
} DATA, *LPDATA;
의 형식은 이미 거의 정형화된 수준이라 볼 수 있습니다. 제가 보기엔 델파이와 빌더를 동시에 오랜 기간 사용하신 숙련자라 볼 수 있지만... TData 등은...대부분(?)의 전세계의 많은 프로그래머들이 이러한 공식(?)은 사용하지 않는 것 같습니다 ^^;;;;;(물론 이것도 제 사견입니다만...) 그렇다고 틀렸다거나 공감하지 않는 것은 아닙니다만 글쎄요... 너무 맹목적인 글이 되지 않을까해서 댓글 남깁니다 ^^'''