가상함수와 테이블 (vtbl)가상함수와 테이블 (vtbl)

Posted at 2011.07.13 23:30 | Posted in About Programing/08. C++

 출처 : http://skmagic.tistory.com/140 / http://zerobell.tistory.com/17

1. 가상함수


 동적 결합을 하는 함수.
 가상함수로 선언하면 포인터의 타입이 아닌 포인터가 가리키는 객체의 타입에 따라 멤버 함수를 선택한다.

virtual 키워드를 선언부에 넣어주면 가상함수가 된다.

#include <iostream>

using namespace std;

class CCellPhone
{
public :
 virtual void CellPhone_1 ();
 void CellPhone_2 ();
};

class CGalaxyS : public CCellPhone
{
public :
 virtual void CellPhone_1 ();
 void CellPhone_2 ();
};

void CCellPhone::CellPhone_1()
{
 cout<<"My phone is cell phone!"<<endl;
}

void CCellPhone::CellPhone_2()
{
 cout<<"My phone is cell phone!"<<endl;
}

void CGalaxyS::CellPhone_1 ()
{
 cout<<"My phone is cell GalaxyS!"<<endl;
}

void CGalaxyS::CellPhone_2 ()
{
 cout<<"My phone is cell GalaxyS!"<<endl;
}

int main (void)
{
 CCellPhone* pGalaxyS = new CGalaxyS();
 CCellPhone* pCellPhone = new CCellPhone ();

 pGalaxyS->CellPhone_1();
 pCellPhone->CellPhone_1();

 pGalaxyS->CellPhone_2();
 pCellPhone->CellPhone_2();

 return 0;
}

글을 읽으면서 위 소스의 출력값에 대해서 생각해 보자.

Binding(바인딩) : 함수 호출문에 대해 실제 호출될 함수의 번지를 결정하는 것.
정적 바인딩 : 함수 호출문에 대한 번지가 컴파일시 이미 정해져 있는 것.
동적 바인딩 : 함수 호출문에 대한 번지가 컴파일 시가 아닌 실행 중에 호출할 함수를 결정하는 것.
이것은 가상함수 테이블을 이용한다. 동적 바인딩은 포인터(또는 레퍼런스)로 호출할 때만 동작함.
포인터가 아니면 어떤 객체에 대해서 함수인지 알수 있으므로 적적바인딩 됢. 당연한 소리.

2. 가상함수 테이블 (vtable)

 가상함수를 가지는 클래스는 가상함수 테이블을 가지고 있고,
클래스 안에서는 이 테이블을 가리킬수 있는 포인터를 가지고 있다.
이 때문에 가상함수를 가지는 클래스는 크기가 4byte 추가 할당 된다.
가상함수 테이블을 가상함수들만 들어가고, 가상함수 주소들의 배열 형태로 존재한다.

 당연한 이야기지만, 한 클래스에서 많은 가상함수를 가지고 있더라도 같은 vtable을 사용한다.
가상함수 테이블을 객체마다 가지는 것이 아니고,
클래스 별로 가지고 있는 것이다.

3. 가상함수를 사용하는 이유

 부모클래스형 포인터로 멤버 함수를 호출할 때, 컴파일러는 정적 타입을 보고,
이 타입에 맞는 멤버 함수를 호출한다.(비가상 함수)
그래서 정적타입이 아닌 동적타입에 따른 함수를 부르기 위해 가상함수를 사용한다.(동적 바인딩)

- 비가상 함수 : 포인터가 어떤 객체를 가리키는가에 상관없이 항상 포인터 타입 클래스의 멤버 함수 호출.
- 가상 함수 : 포인터가 가리키는 실제 객체의 함수를 호출.

이제 위 소스의 출력값에 대해서 유추 할 수 있을 것이다.


그래서!
재정의 할 가능성이 있는 멤버 함수는 가상함수로 선언하는 것이 좋다.

보통은 재사용가능성이 많은 소멸자를 가상함수로 만들어 준다.
상속 관계에서 자식 클래스의 소멸자는 반드시 가상으로 선언해야 한다.
저작자 표시 비영리 변경 금지
신고

Name __

Password __

Link (Your Website)

Comment

SECRET | 비밀글로 남기기

Kyuseo's C++ 프로그래밍 스타일 가이드라인 :: 코딩/프로그래밍 규칙C++ 팁, 강좌Kyuseo's C++ 프로그래밍 스타일 가이드라인 :: 코딩/프로그래밍 규칙C++ 팁, 강좌

Posted at 2010.09.21 13:41 | Posted in About Programing/08. C++
Kyuseo's C++ 프로그래밍 스타일 가이드라인 ::코딩/프로그래밍 규칙
Kyuse's C++ Programming style guideline (Coding / Programming rule)
버전 : 2.2
작성자 : 채경석 Kyuseo의 게임 프로그래밍 이야기 :: http://a.TK.co.kr
저작권 : 출처 및 링크를 표시하여 자유롭게 사용합니다. 

업데이트 :

날짜

장소

내용

1997

Skyteam 

기본 개념 작성

2000~2004 

Sky Soft

전반적 이론 및 규칙 확립

2005- 4- 13

Freechal

체계화된 정리

2006-09-12

Fungrep

1차 개정

2008-01-24

Fungrep

2차개정

2008-01-31

 

띄어쓰기 및 보기 좋게 편집 변경

 

개요..

 본 가이드는 절대적이거나, 표준으로 규정된 내용들이 아니라 하나의 지침사항입니다.
 프로그래밍에 조금의 지식이 있는 프로그래머라면 '(' 뒤에 공백이 있던지 '{'를 어떻게 붙이던지 코드를 분석하는데 별다른 어려움이 없습니다.
 하지만 가이드를 힘들게 작성하고 배우고 지켜야 하는 이유는, 좀 더 코드를 분석하는 시간을 줄이고 이해하기 쉽도록 하기 위해서 입니다. 이것이 동일한 프로젝트에서, 같은 팀에서, 같은 스타일로 제작해야 하는 이유입니다.
 
아래 사항들은 표준도 아니고 절대적이지도 않기 때문에 일부분을 자신들의 팀이나, 프로젝트 스타일에 바꾸어 수정해서 사용하셔도 무방합니다. 

규칙으로 작성한 프로그램 코드의 예

 

 

1 이름


1.1    변수의 표기는 헝가리안 표기법을 사용한다.
(헝가리안 표기법이 타이핑하기 불편하거나 귀찮을 수도 있으나 근본적으로 변수의 명확성을 강화한다.)

예)     int nNumber, char szName, CString strInfo, CSurface surSportsCar, int *pnData, CWnd wndMain

1.2    각 변수 및 함수의 이름은 대문자로 시작한다.
(소문자만을 이용하거나 '_'을 이용하거나 또는 소문자 + 첫 단어 대문자를 이용하는 많은 사용 예가 있기는 하지만 결국 여러 스타일을 동일 프로젝트에서 혼용하여 사용하는 것은 통일성과 가독성 등에 저해를 가져온다. 때문에 다양한 방법 중 한가지를 선택할 필요성을 느꼈고, 첫 문자를 대문자로 쓰는 것으로 통일하였다.)
예)    int nTypeMax, char szFileInfomation, (O)
int ntype_max, char szfile_infomation (X)
int GetType(); BOOL SetMaxRange( int CX, int CY ), void SetFont( HFONT hFont )

1.3    헝가리안 표기법 중 포인터는 중첩하여 사용 가능하다.
(포인터 뿐만이 아니라 배열의 'a' 나 다른 의미의 명확성을 위해서 몇몇 헝가리안 표기법도 중첩사용이 가능하다.)
예)    int *pnCount, CSurface *psurBack, BYTE *pBuffer

1.4    헝가리안 표기법의 예외는 다음과 같다.
- X, Y 와 같은 의미가 명확한 변수 ( 확장 : XDest, YDest )
- For 문 등에서 사용하는 I, j 등의 이미 알려진 변수
- 구조체 멤버 변수
- BYTE *pBuffer 와 같이 BYTE 라는 값이 큰 의미가 없는 경우
- 구조체 변수의 경우
(헝가리안 표기법이 만능도 아니고 절대적인 것도 아니다. 단순히 습관적이고 일반화된 선례 등은 예외가 될 수 있다.)

1.5    전역 변수는 'g_' 을 사용한다.
(지역변수 및 인자와 구분을 위해서 'g_'을 사용한다. 참고로 전역 변수는 가능한 사용을 자제한다.)
예)     int g_nGameTimer, char g_szAppName

1.6    클래스의 멤버 변수는 'm_' 을 사용한다.
(지역변수 및 인자와 구분을 위해서 'm_'을 사용한다.)
예)     m_surBack, m_szName, m_dwCode

1.7    포인터(*) 및 참조(&) 표기는 변수 앞에 붙여서 선언한다.
(변수 앞에 붙여서 표기 하는 가장 중요한 이유는 char* szName, szType 과 같이 표기 할 경우 szType 이 포인터인지 아닌지 구분이 잘되지 않기 때문이다.)
올바른 예)
POINT *pPoint, char *szName;
POINT &pPoint, char &szName;

잘못된 예)
POINT* pPoint, char * szName;
POINT& pPoint, char & szName;

1.8    #define enum 및 const 는 대문자와 단어 사이에 '_' 를 붙인다.
예) #define WM_USER_PING_THREAD_END WM_USER+0x102
#define PACKET_LOGIN_ANSWER_BAD_USER_OVER    8
enum BUTTON_STATE{ BUTTON_NORMAL, BUTTON_OVER, BUTTON_DOWN, BUTTON_DISABLE };

1.9    #define 보다 const 형 또는 static const 형을 되도록 사용한다. (#define 과 같은 대문자 선언 시에 사용)
(하지만 배열 등에 직접적으로 const 형은 컴파일 오류가 발생하기 때문에 현재 보류한다.)
예) static const int MAX_CARD = 1000

1.10    클래스 내부에서 사용하는 상수 값은 클래스 내부에서 선언된 enum을 사용한다.
(#define은 전역적으로 사용된다. 따라서 MAX_ITEM과 같은 일반적인 상수이름은 다른 곳에서 겹칠 우려가 있기 때문에 사용하기가 꺼려진다. 하지만 enum은 클래스 내부에서만 사용되기 때문에 MAX_ITEM와 같은 일반화된 상수를 사용할 수가 있다.)

1.11    구조체 이름은 대문자로 하고 각 단어 구분은 '_' 로 한다.
(구조체의 이름은 관습적으로 대문자만을 사용한다.)
예) struct TEST{ … }
struct BANNER_UNIT{ … }, struct GAME_ITEM{ … },

1.12    구조체의 멤버 변수는 소문자를 이용하고 단어 별 구분 시에 '_' 을 사용한다.
(헝가리안 표기법이 사용되지 않는 이유는 오랜 기간 관습적으로 소문자 표기법이 이용되었기 때문이다.)
예) struct GAME_UNIT
{
    BYTE type;
   
char id[13];
   
WORD version;
   
BYTE* buffer; 
    CString ip_name,
};

1.13    일반적으로 지역 변수는 선언된 타입과 동일한 이름을 사용한다.
(클래스나 구조체등의 이름을 이용하여 지역 변수를 선언하면 알아보기가 쉽고 변수명 작명을 위해 고민하지 않아도 된다.)
예) BANNER_UNIT BannerUnit, FAST_TABLE FastTable, CSkyPassword SkyPassword

1.14    템플릿 타입은 대문자 한 자로 표기한다.
(관습에 의하여 템플릿은 한 자로 표기한다.)
예) template< class T>, template< class C, class D >

1.15    클래스를 이름은 첫 자를 C 로 시작한다.
(클래스를 구분하는 헝가리안 표기법의 일종이다.)
예)     CSurface, CPoint

1.16    클래스 함수의 경우 클래스 명에서 이미 명시된 부분은 제외한다.
(이미 클래스 이름으로도 충분히 알 수 있는 내용을 중복하여 적는 것은 비효율적이며 올바르지 않는 방법이다. 이것은 마치 "사각형의 사각형 가로크기는 얼마인가요?" 와 같은 표현이다.)
올바른 예)     Rect.GetWidth()
잘못된 예)     Rect.GetRectWidth()

1.17    역 BOOL형 변수는 사용하지 않는다. (역방향형)
(빠른 이해와 잘못된 사용을 방지하기 위해서 BOOL은 항상 '예' 인 값을 사용한다. 역 BOOL 형 변수는 '사람이 아니다가 아니다' 와 같은 복잡한 상황을 연출하기 때문이다.)
올바른 예)     BOOL bError
잘못된 예)     BOOL bNoError

1.18    Enum의 경우 열거형으로 사용시 변수명의 앞에 표기한다. (단 클래스 내부에서 #define의 대용으로 사용시에는 예외이다.)
(enum을 열거형으로 사용시 타입을 항상 표기함으로써 값들이 사용시 어떠한 열거형 인지 구분을 쉽게 해준다.)
예)
enum COLOR
{
    COLOR_RED, 
    COLOR_GREEN, 
    COLOR_BLUE
};

1.19    함수명의 경우 동사가 앞에 오고 명사가 뒤에 오도록 한다.
(영문법에 맞는 규칙으로 통일되게 사용한다.)
예)    GetLine(), PlaySound(), MoveWindow()

1.20    BMP나 MP3, ID, XML등의 약어의 표현은 첫 대문자로 한다.
예)     CBmpFile, m_Mp3, strUserId, CXml

2 일반

2.1    bool, true, false 는 사용하지 않고 BOOL, TRUE, FALSE 로 사용한다.
(BOOL등은 4바이트 자료형이고 bool등은 1비트(실제로는 1바이트) 자료형이다. 32비트 운영체계의 경우 속도와 비교하는데 걸리는 성능 부분에도 유리하고 알아보기도 좋기 때문에 위와 같은 대문자로 된 자료형을 사용한다.)

2.2    한 라인이 너무 길지 않도록 주의한다.
(정확히 컬럼의 크기가 정해진 것은 아니지만 일반적인 프로젝트 팀에서 사용하는 일반적인 해상도(1024~1280)에서 한눈에 보기 좋도록 작성한다.)

2.3    성능 향상 및 가독성 증가를 위해서 본 가이드라인은 위배 될 수 있다. (단! 확고한 근거가 존재해야 한다)
(가이드라인의 목표는 보기 좋고 통일된 코드작성이 가장 중요한 목표이다. 특수한 경우 가독성이나 성능 향상을 위해서 더 좋은 방법이 있다. 그러한 경우에는 본 가이드라인을 따르지 않는다.)

2.4    변수의 형 변환은 (자료형) 과 같은 변환을 사용한다.
(일반적으로 여러 서적과 C++ 권고안에서 static_cast, const_cast등의 cast를 이용한 형 변환을 권고하고 있으나 타이핑의 불편, 가독성의 감소, 다양한 cast의 적절한 사용 등으로 인한 이유로 사용하는데 어려움이 있다.)

예)
int nSize = ( int ) byLength; (O)
int nSize = const_cast<int> byLength; (X)

2.5    함수의 인자는 NULL 입력이 가능하다면 포인터(*)를 이용하고 그렇지 않다면 참조(&)을 사용한다.
(포인터 방식의 최대 단점은 NULL을 원하지 않은 경우에도 NULL이 입력될 수 있다는 점이다. 따라서 이러한 것을 근본적으로 막기 위해서는 NULL이 입력되지 않기를 원하는 함수의 인자는 참조를 사용한다.)

예)     int Draw( CSurface &rSurface );               (NULL이 입력 불가한 경우)
void GetValue(int *pId, int *pAge, int *pDate = NULL ); (NULL이 입력 가능한 경우)

2.6    전역함수, API함수, 표준 C++ 라이블러리의 경우 일반적으로 '::' 을 가능한 사용하지 않는다.
(멤버 함수와 동일한 이름인 경우에만 '::' 을 사용한다, (이와는 별개로 전역 함수의 사용은 최소한 한다.)
올바른 예)     int nCount = ::GetWindowCount();     int nSize = strlen( szName );
잘못된 예)     int nCount = GetWindowCount();    int nSize = ::strlen( szName );

2.7    STL 사용시 std:: 는 사용하지 않는다.
(프로젝트에서 STL을 표준으로 사용하기를 결정했다면 'using std;'을 사용한다.)
올바른 예)    std::list List
잘못된 예)     list List

2.8    지역변수는 필요한 시점에서 선언한다.
(C++에서는 변수를 미리 선언할 필요성이 없다. 단 기능이나 사용이 비슷한 변수들이 선언된다면 가독성 증가를 위해서 미리 선언 될 수 있다.)

2.9    매직 넘버(Magic Number)는 가능한 사용하지 않는다.
(가능한 #define 이나 enum, static const 등을 이용하여 매직 넘버의 사용을 최소화한다.
개수, 최대값, 최소값, 범위 등의 매직 넘버는 사용을 최대한 자제하나 오히려 가독성과 사용용도가 떨어지는 이미지의 좌표값과 같은 일시적인 것은 특별히 사용을 회피 할 필요는 없다. 또한 0, 1, -1의 경우는 매직 넘버에서 예외가 될 수 있다.)

2.10    goto의 사용을 자제한다.
(goto의 사용은 프로그램의 구조를 파악하기 어렵게 한다. 하지만 매우 특수한 경우에는 사용 될 수 있다.)

2.11    전역 변수나 멤버 변수보다는 지역변수를 사용한다.
(전역 변수와 멤버 변수는 매우 오랜 기간 동안 생존해있고 중요한 변수이기 때문에 작성자를 포함한 프로그래머들은 그 변수들을 분석하고 기억하려고 한다. 따라서 사용을 최소화 한다면 그러한 수고가 줄어든다.)

2.12    포인터, 핸들은 NULL과 비교하고 숫자는 0과 비교한다.

2.13    복잡한 구문은 임시 변수를 사용한다.
(부적절한 3항 연산자나 각종 비교구문의 중첩을 이용한 복잡한 코드는 가독성을 저해한다.)
올바른 예)
BOOL bCompare1 = ( nCount > 100 && nSize < 50 );
BOOL bCompare2 = ( nCount == 50 || nSize < 20 );
if( bCompare1 || bCompare2 )…

잘못된 예)
if( ( nCount > 100 && nSize < 50 ) || ( nCount == 50 || nSize < 20 ) )…

3 클래스

3.1    클레스의 기본 구조는 다음과 같다.
(생성자, 공개함수, 비공개함수, 멤버변수의 순서로 배열하고 각각은 public, protected 등으로 추가로 명기하여 구분한다. )
예)
class CSound
{
public:         : public 함수를 최 상단에 배치한다.
CSound();     : 생성자를 최 상단에 배치한다.
virtual ~CSound();        : 종결자는 일반적으로 virtual 이다. 
void Play();        : 멤버 함수
void Stop(); 

: public, protected 등은 1라인 비운다.
protected:            : protected 함수를 배치한다.
void PlayWav();         
void PlayOgg();
: 각각의 함수별 관련성 및 블록이 있다면 1라인 비운다.

void StopWav();
void StopOgg(); 

protected:             : 멤버 변수를 배치한다.
char m_szFileName[256];
int m_nSize;
};

3.2    모든 멤버 변수는 public으로 선언하지 않는다. 
(정보은닉과 캡슐화를 위해서 멤버 변수들은 public으로 선언하지 않는다.) 

3.3    inline 함수의 경우 헤더 선언에 inline 을 표기하지 않는다. 
(헤더 파일에 inline으로 명기하여 선언을 하는 것과 별개로 실제 구현에 inline으로 작성하면 함수는 inline으로 작동된다. 클래스 헤더를 잘 알아볼 수 없게 하는 inline은 굳이 사용 할 필요성이 없다.) 
예)
class CSound
{
올바른 예)     BOOL Play();
잘못된 예)     inline BOOL Play();     
}; 

3.4    inline 함수의 경우 선언구문 1라인 아래 줄에 입력한다. 
(inline 함수는 헤더 파일의 하단에 선언하고 너무 많은 inline 함수가 존재한다면 *.ini 파일을 작성하여 헤더에서 include 한다.) 
예)
class CSound
{
public: 
    BOOL CSound ::Play();
}; 

inline BOOL CSound ::Play()
{



또는 
c
lass CSound
{
public: 
    BOOL CSound ::Play();
}; 

#include "Sound.inl" 

3.5    클래스 선언문에 어떠한 함수라도 구현을 하지 않는다. 
(클래스의 선언문은 매우 중요한 공간이다. 그 공간에 함수의 구현이 들어간다면 함수의 이름들을 한눈에 볼 수 없어 가독성이 매우 떨어지게 된다. 따라서 모든 구현은 cpp 파일 및 별개의 인라인 함수로 구현한다.) 
잘못된 예)
class CTest
{
BOOL Play()
{

}
}; 

3.6    클래스의 생성자 및 종결자는 inline함수로 구현하지 않는다. 
(성능향상을 위한 몇몇 경우를 제외하면 생성자와 종결자는 호출되는 회수는 매우 적다. 따라서 inline으로 구현하지 않는다.) 

3.7    종결자는 일반적으로 virtual로 선언한다. 
(종결자가 virtual이어야 상속될 경우 등에서 올바르게 종결자가 작동된다. 따라서 상속이 되지 않기를 바라는 경우와 클래스 크기를 줄이기 위한 경우 및 성능향상을 위한 경우를 제외하고 가상함수로 선언한다.) 
 

4 파일 및 디랙토리 ..

4.1    클래스명과 파일명은 'C'를 제외한 동일한 이름을 가진다. 
예)     클래스명 : CFileName 
         파일명 : FileName.h, FileName.cpp 

4.2    1클래스 2파일(.h .cpp)을 가진다. 

4.3    클래스의 복잡도에 따라서 여러 개의 cpp 파일을 가질 수 있다. 
(종종 멤버 함수가 많다면 함수의 검색 및 수정 등이 불편하여 관리가 어려워진다. 그러한 경우 비슷한 함수 별로 파일을 만든다면 쉽게 관리가 가능하다.) 
예)     클래스명 : CSurfaceGdi 
    파일명 : SurfaceGdi.h, SurfaceGdiPut.cpp SurfaceGdiInit.cpp, SurfaceGdiEffect.cpp 

4.4    Inline 함수가 많이 존재할 경우 '.ini' 파일을 생성한다. 
예)     FileName.inl 

4.5    매우 간단하면서 비슷한 여러 개의 클래스 및 구조체의 경우 1개의 파일에 포함될 수 있다. 
(작은 클래스, 구조체마다 각각의 파일을 가진다면 파일개수의 복잡성이 증가한다.) 

4.6    문서(기획서, UML등) 파일은 소스파일과 다른 특정 디렉토리를 사용한다. (예:Document) 
(각종 문서들을 소스파일과 함께 관리한다면 더욱더 복잡한 파일목록들이 만들어질 것이다.) 

4.7    일정 규모이상의 프로젝트의 시작은 폴더구조를 설계한 이후 시작한다. 
(프로젝트 시작 시 폴더의 구조와 각 폴더 별 연관성 등을 고려하여 폴더구조를 정의하고 상호 참조 관계 등을 정의하는 것으로 프로젝트를 시작해야 한다.) 
예)
- [App]
- [Common]
- [Core]
- [Sound]
- [Image]
- [Document] 

4.8    #include 시 상대주소를 사용한다. 예) #include '../BugGame/BugMain.h' 
(절대주소를 사용한다면 다른 프로그래머들은 컴파일이 안될 수도 있기 때문이다.) 

4.9    #include 는 시스템 헤더 -> 라이브러리 -> 자체 코드 등의 순서로 작성한다. 
(#include 또한 상위 -> 하위 관계를 지키도록 하면 가독성이 높아진다.) 
예)
#include 'stdio.h'
#include 'afxinet.h'
#include 'MyImageEngine.h'
#include 'MyFile.h' 

4.10    백업은 다른 디스크드라이브에 받고 일정기간마다 CD 또는 별개의 매체에 저장한다. 
(백업의 중요성은 아무리 강조해도 지나치지 않다.) 

4.11    프로그램 코드, 이미지, 사운드를 포함한 모든 소스는 소스세이프(SourceSafe) 및 Subversion, SVN등의 소스 관리를 사용한다. 
(다른 프로그래머가 소스 관리을 이용하더라도 컴파일과 실행을 할 수 있게 한다.) 
 

5 레이아웃..

5.1    변수 또는 함수 선언 시 TAB으로 정렬하지 않는다. 
(TAB문자를 이용한 정렬은 보기가 좋기는 하지만 결국 전체 스타일은 망가뜨린다. 또한 특정 변수의 추가 / 제거시 다른 변수들의 탭이 엉클어져 다시 정돈하는 과정이 필요하므로 불필요한 편집 작업을 늘린다.) 
올바른 예)
int nCount;
int nSize;
BOOL bSuccess;
CString strName; 

잘못된 예)
int         nCount;
int         nSize;
BOOL        bSuccess;
CString     strName; 

5.2    가독성을 이유로 다음과 같은 정렬 방식은 가능하다. 
예)
int nSum =     a + b + c + 
        d + e;
if(    nCount > 100 && nSize > 50 && 
    nHit == 5 ) 

5.3    TAB의 크기는 4글자로 한다. 

5.4    함수마다 1라인을 띄운다. 

5.5    '+, -, *, %, /, <, >, =, ==, +=, -=' 와 같은 2개의 변수를 계산하거나 비교하는 경우에는 연산자의 전후는 1칸씩 띄어 쓴다. 
(변수 단독 붙여서 사용하는 '++, --, !, &(참조), *(포인터)'와 같은 연산자의 경우는 예외이다.) 

올바른 예)
i = a + b – c;
nSum += nCount;
if( nSize < nMaxSize )
i++; 

잘못된 예)
i=a+b–c;
nSum+=nCount;
if( nSize<nMaxSize )
i ++; 

5.6    일반적으로 i++ 은 사용이 가능하지만 ++i 는 사용하지 않는다. 
(속도를 위해서 후자를 사용하는 사례들이 가끔 있지만 사실상 컴파일러의 최적화로 인하여 위 내용의 속도의 차이가 존재하지 않는다. 따라서 가독성과 통일성을 이유로 전자만 사용한다.) 

5.7    소괄호의 사용은 다음과 같이 공백입력을 한다. (함수, if, while, for, switch, 형 변환 등 모두 동일함) 
예)
if( bSuccess == TRUE ) …
int DoSomething( int nParam1, int nParam2 );
for( int I = 0; I < nCount; I++ )
int nIntSize = sizeof( int );
int nValue = ( int ) byValue; 

5.8    중괄호의 사용은 다음과 같은 형식으로 한다. (함수, if, while, for, switch 모두 동일함) 
올바른 예)
if( bSuccess == TRUE )

    DoSomething(); 
    …


int DoSomething()

    …


잘못된 예)
if( bSuccess == TRUE ){ 
    DoSomething(); 
    …


if( bSuccess == TRUE ) 
    { 
    DoSomething(); 
    … 
    } 

5.9    가독성을 위하여 다음과 같이 TAB으로 정렬이 가능하다. 
(본 가이드의 핵심은 가독성이 좋은 코드를 만드는 것을 도와주는 것이다. 따라서 그것을 위해서 몇몇 규범들은 위배 될 수 있다. 하지만 아래내용을 절대로 남용하지 않는다.) 
예)
if(         nCount == 0 )         DoSomething();
else if(    nCount == nMaxCount )    DoSomething();
else                    DoSomething(); 
nValue =     ( nSize         * nCurrentSize )        / nDiv + 
        ( nCount     * nCurrentCount )    / nDiv + 
        ( nTime     * nCurrentTime )        / nDiv; 

switch( nState )

    case STATE_NORMAL :     DoNormal(); break; 
    case STATE_OVER :     DoOver(); break; 
    case STATE_DOWN :     DoDown(); break;


5.10    Switch 구문은 다음과 같은 형식으로 한다. (':' 의 띄어쓰기에 유의한다.) 
예)
switch( nState )
{
// case 관련 주석은 여기에…
case STATE_NORMAL : 
    // 아래 주석은 여기에… 
   DoSomething(); 
    break; 

case STATE_OVER : 
    DoSomething(); 
    break; 

default : 
    DoSomething(); 
    break;


5.11    한 줄 조건문 및 반복문은 다음과 같이 모두 사용 할 수 있다. 하지만 동일 구문에 여러 스타일을 혼용하여 사용하면 안된다. 
올바른 예)
if( bSuccess == TRUE ) DoSomething();
else DoSomething(); 

if( bSuccess == TRUE ) 
    DoSomething();
else 
    DoSomething(); 

if( bSuccess == TRUE )

    DoSomething();
}
else

    DoSomething();


잘못된 예)
if( bSuccess == TRUE ) DoSomething();
else

    DoSomething();

i
f( bSuccess == TRUE ) DoSomething();
else 
    DoSomething(); 
if( bSuccess == TRUE )

    DoSomething();
}
else    DoSomething(); 

5.12    'return' 의 윗라인은 1줄 여백으로 한다. 
올바른 예)
BOOL IsSuccess()

    BOOL bRtn = m_bRtn; 

    return bRtn;
}

잘못된 예)
BOOL IsSuccess()

    BOOL bRtn = m_bRtn; 
    return bRtn;


5.13    중괄호를 닫는 부분과 if, for등의 조건, 반복 구문에서는 1줄 여백을 가진다. 
(적절한 줄 여백은 코드를 읽기 쉽게 한다. 참고로 if-else구문은 else에만 적용한다.) 
올바른 예)
if( bSuccess == TRUE )

    …
}
else

    …


nSum += nCount; 

잘못된 예)
if( bSuccess == TRUE )

    …
}
nSum += nCount;  

6 If / switch / for / while / switch 구문..

 6.1    If / switch / for / while 구문 자체에서 복잡한 계산을 하지 않는다. 
(조건문 내의 복잡한 계산은 코드의 가독성을 저해한다.) 

올바른 예)             // 잘못된 예)
nSum = 0;            //
for( int I = 0; I < 100; I++ )    // for( int I = 0, nSum = 0; I < 100; I++, nSum += I; )
{                // { 
    nSum += I;        // 
    printf( "%d", nSum );    //     printf( "%d", nSum );
}                // } 

hFile = open( szFileName, 'w' );// if( hFile = open( szFileName, 'w' ) )…
if( hFile )            // {
{                // }
}                 // 

6.2    Do – While 은 가능한 사용하지 않는다. 
(While만을 사용하는 것이 가독성이 증가된다.) 

6.3    무한 루프 구문은 while( TRUE ) 또는 while( 1 )을 사용 한다. 
올바른 예)
while( TRUE )
{


while( 1 )
{


잘못된 예)
for( ; ; )
{


do
{
} while( 1 ) 

6.4    BOOL및 HRESULT 등의 리턴 값을 TRUE, 0 과 비교하지 말고, ! 이나 비교 없이 단독으로 사용한다. 
(이것은 몇몇 버그가 있는 API 함수들을 위한 것이다. 즉 매뉴얼 상에는 리턴 값이 BOOL 이더라도 0 과 1 을 리턴 하지 않는 경우가 종종 있다. 따라서 일반적으로 0 인가 0 이 아닌 가로 구분하거나 본 구문과 같이 처리하는 것이 일반적이다. ) 
올바른 예)
if( IsMain() ) …
if( !IsMain() ) … 

잘못된 예)
if( IsMain() == TRUE ) … 

6.5    BOOL및 HRESULT 등의 리턴 값을 TRUE, FALSE, SUCCEED(), FAILED()등 과 비교한다., 
(코드의 간소함보다 '!' 이 눈에 잘 띄지 않으므로 오히려 가독성을 저해하고 코드 실수를 유발할 가능성이 크므로 이와 같이 변경하였다. ) 
올바른 예)
if( IsMain() == TRUE ) …
if( IsMain() == FALSE ) … 

잘못된 예)
if( IsMain()) …
if( !IsMain()) …  

7 주석 (자세한 내용은 kyuseo's 주석 스타일 가이드라인 참고)

7.1    주석은 모국어(한국어)로 작성한다. 

7.2    주석은 Doxygen 표준을 사용한다. 

7.3    복잡성에 따라서 세부적인 설명 및 예제를 포함할 수 있다. 

7.4    일반적인 주석의 경우 /*, */ 보다는 가능한 // 으로 주석을 사용한다. 
(Doxygen 의 경우를 예외로 한다.) 

7.5    주석은 내용의 앞 라인과 같은 컬럼에 기록한다. 
(몇몇 특수한 상황에서 라인 뒤에 입력하는 경우도 있다.) 
올바른 예)
// 성공했다면…
if( bSuccess == TRUE )

    // 어떤일을 한다. 
    DoSomethinh();


잘못된 예) 
// 성공했다면…
if( bSuccess == TRUE )
{
// 어떤일을 한다. 
    DoSomethinh();


if( bSuccess == TRUE ) // 성공했다면…

    DoSomething(); // 어떤일을 한다. (X)
}  

8 권장 함수명

일반적으로 생성, 초기화하는 부분은 BOOL 을 리턴 하고 파괴, 제거, 닫는 부분은 void 형 함수로 처리한다.
권장하는 함수명 뒤에 CreateImage, OpenFile과 같이 각종 명사를 붙여서 사용 할 수 있다.  

함수명

용도

Create / Destroy

생성 / 파괴

Open / Close

열다 / 닫다

Init / Final

초기화 / 마지막

Play / Stop

시작 / 정지

Get / Set

얻음 / 넣음

Add / Remove

추가 / 삭제 (일반적으로 순서는 상관 없다)

Insert / Delete

(중간에) 삽입 / (중간에) 삭제

Start / Stop

시작 / 정지

Suspend / Resume

일시 정지 / 재 시작

Begin / End

시작 / 끝

First / Last

처음 / 끝

Next / Previous

이전 / 다음

Increment / Decrement

증가 / 감소

Old / New

이전 / 새로

Up / Down

상 / 하

Min / Max

최소 / 최대

Show / Hide

보여줌 / 가림

 

참고

 http://geosoft.no/development/cppstyle.html
http://blog.naver.com/iliyard/9298516
http://blogs.msdn.com/brada/articles/361363.aspx
Effective C++, Scott Meyers
More Effective C++, Scott Meyers
Code Complete, Steve McConnell

저작자 표시 비영리 변경 금지
신고

Name __

Password __

Link (Your Website)

Comment

SECRET | 비밀글로 남기기

객체지향 프로그래밍의 4대 특징객체지향 프로그래밍의 4대 특징

Posted at 2010.01.31 13:05 | Posted in About Programing/08. C++

내가 객체지향을 공부했어!
객체지향 프로그래밍엔 빠삭해!
XXX 객체지향 언어에 대해서는 모르는 게 없다!
 
이렇게 말할 수 있으려면 객체지향의 4대 특징을 이야기 할 수 있어야 하고 그 객체지향 언어가 어떤 문법을 통해 4대 특징을 지원하는 지 열거할 수 있어야 합니다. 그 만큼 객체지향 프로그래밍의 4대 특징은 객체지향의 핵심중의 핵심입니다.
 
객체지향 프로그래밍의 첫번째 특징은 추상화 (abstraction)입니다. 추상화라고 하는 것은 객체들의 공통적인 특징(속성과 기능)을 뽑아내는 것입니다. 즉, 우리가 구현하는 객체들이 가진 공통적인 데이터와 기능을 도출해 내는 것을 의미합니다.
 
그럼 추상화가 객체지향만의 특징이냐? 그렇지 않습니다. 추상화라고 하는 것은 절차지향 프로그래밍에서도 있어왔습니다. 대표적인 것이 구조체와 같은 사용자 데이터 형입니다. 이것은 데이터를 추상화해서 하나의 새로운 데이터 유형을 만드는 것입니다. 예를 들면 국어, 영어, 수학 점수를 모아 하나의 데이터 형으로 만든다면 아래와 같이 정의할 겁니다.
 
struct Sungjuk
{
  int kor_score;
  int eng_score;
  int math_score;
};
 
Sungjuk이라는 새로운 이름으로 데이터형을 정의하는 데 그 부속데이터로 kor_score, eng_score, math_score를 두고 있습니다. 성적이라는 데이터를 추상화를 통해 정의한 것입니다.
기능의 추상화도 있습니다. 함수가 바로 그것인데, 잘 쓰는 기능을 이름을 붙여 정의하였으니 기능을 추상화 한 것이죠. 여기서 데이터의 추상화와 기능의 추상화에서 알 수 있는 하나의 사실이 있습니다. 추상화라고 하는 것에는 항상 새로운 이름이 붙는다는 점입니다. 따라서 프로그래밍 입장에서 추상화라고 하는 것은 이렇게 정리해 볼 수 있습니다.
 
‘공통의 속성이나 기능을 묶어 이름을 붙이는 것’
 
객체지향 프로그래밍에서 추상화는 그럼 무엇일까요? …
네 생각하는 대로 입니다. 클래스를 정의하는 과정이 바로 추상화라고 할 수 있습니다. 추상화관련 문법은 다음 장에서 다루기로 하고 일단 여기서 추상화 설명을 마치겠습니다.
 
 
두번째 특징은 바로 캡슐화(encapsulation)입니다. 객체지향에서 캡슐화는 데이터 구조'와 `데이터를 다루는 방법'를 결합시켜 묶는 것을 말합니다. 캡슐형태로 된 알약을 떠 올리시면 됩니다. 캡슐형 알약에는 질환을 치료하는 데 필요한 다른 종류의 약이 적정한 비율로 섞여있는 것을 볼 수 있습니다. 해당 질환을 치료하기 위한 목적으로 하나가 아닌 몇 가지 기능의 약을 하나의 캡슐안에 담은 겁니다.
객체지향에서 캡슐화도 마찬가지 입니다. 특정 객체가 독립적으로 역할을 제대로 수행하기 위해 필요한 데이터와 기능을 하나로 묶어 관리합니다. 객체가 맡은 역할을 수행하기 위한 하나의 목적을 위해 데이터와 기능들을 묶는 것이죠.
 
그런데 캡슐화라는 특징은 다른 의미로도 쓰입니다. 즉, 데이터는 은닉하고 그 데이터를 접근하는 기능(함수)를 밖으로 노출한다는 의미를 나타낼 때 캡슐화라는 용어를 씁니다. 데이터를 기능이라는 캡슐로 보호한다는 것이죠. 
 
 
 

위 그림처럼 데이터를 기능으로 보호하여 데이터의 입출력이 기능을 통해 통제되도록 설계하는 것을 말합니다. 역시 자세한 것은 뒤에 설명될 겁니다.
 
 
세번째 특징은 상속성(inheritance)입니다. 객체지향의 꽃입니다. 상속이 없으면 객체지향은 절차지향과 큰 차이가 나지 않습니다. 객체지향 문법이 어렵고 복잡해 질 이유도 없죠. 상속이란 특징을 가지면서 객체지향 언어가 절차지향 언어에 비해 문법의 규모가 2배이상 늘어났습니다.
 
상속이란 상위개념의 특징을 하위 개념이 물려받는 특징을 말합니다. 예를들어 ‘남학생’이라는 단어에는 ‘학생’이라는 단어의 특징을 물려받고 있습니다. 개념적 특징이 상속되는 것이죠. ‘학생’이 갖는 특징을 그대로 계승하면서 ‘남학생’만 갖는 특징을 부가하여 ‘남학생’의 전체 특징이 형성됩니다. 이 개념을 프로그래밍에 도입한 것인데 하나의 클래스가 가지고 있는 특징(데이터와 함수)들을 그대로 다른 클래스가 물려주고자 할 때 상속성의 특징을 사용합니다.
 
 
네번째 특징은 다형성(polymorphism)인데 객체지향 개념 중 가장 까다롭고 4대 특징중에 가장 이해하기 어려운 특징입니다. 알았다고 해도 코딩으로 잘 활용이 안 되는 특징이기도 하죠. 이것을 잘 이해하고 활용한다면 객체지향 언어를 제대로 쓰는 것으로 봐도 무방할 정도입니다.
 
다형성의 의미는 약간 다른 방법으로 일을 하는 함수를 동일한 이름으로 호출해 주는 것을 말합니다. 이해가 잘 안 되죠?  예를 들어 보겠습니다. 홍길동이란 이름의 학생과 김철수라는 학생이 있다고 가정해 봅시다. 그런데 선생님이 홍길동을 바라 보면서 ‘학생! 칠판 지워’라고 명령을 했다고 합시다. 홍길동은 나름의 방법으로 칠판을 지울 겁니다. 다시 선생님이 김철수를 바라보면서 ‘학생! 칠판 지워!’라고 명령했을 때 김철수도 자신만의 방법으로 칠판을 지울 겁니다. 그런데 홍길동이 지웠던 방식과는 다르겠죠? ‘학생! 칠판 지워.’라는 표현은 같지만 칠판을 지우는 행위는 다르게 나타나고 있습니다. 이것이 바로 다형성입니다. ‘학생! 칠판 지워.’라는 명령이 다양한 결과로 나타나기 때문에 이것을 다형성이라고 하는 것입니다.
저작자 표시 비영리 변경 금지
신고

Name __

Password __

Link (Your Website)

Comment

SECRET | 비밀글로 남기기

티스토리 툴바