C++


그다지 책에는 설명이 안되어있는 virtual 키워드...
솔직히 일본에서 일할때 그쪽에선 특별히 뭔가 상속하면서 클래스를 만드는걸 꺼려서해 그런지...
virtual 키워드를 왠만하면 쓰지 않았기 때문에 프로그래밍으로 밥먹고 사는 저도 가물가물한 놈입니다.

잡설은 그만하고
virtual 과 override 차이만 얘기 합니다.
만약

/************************************************************************/
/*상위클래스                                                            */
/************************************************************************/
class upClass
{
public:
  void print(){
  cout<<"up class"<<endl;
 }
};
/************************************************************************/
/* 상속 A                                                               */
/************************************************************************/
class aClass : public  upClass
{
public:
 void print(){
  cout << "aClass"<<endl;
 }
};
/************************************************************************/
/* 상속 B                                                               */
/************************************************************************/
class bClass : public upClass
{
public:
 void print(){
  cout <<"bClass"<< endl;
 }
};

이렇게 해놓았다면
AClass 클래스를 만들어 print()를 호출할때 상속 받은 UpClass의 print함수가 아닌
AClass의 print 함수를 불러오는건 프로그래머로썬 상식입니다.

그리고 이걸 AClass함수가 원래 UpClass를 상속해 존재한 같은 이름의 print 함수를 덮어 씌었기 때문에 override라고 합니다.

보통 프로그래밍 할땐 문제가 없지만
만약 경우에 따라 A,B클래스를 따로 부르고 싶을땐 문제가 발생합니다.

예를들어
//메인 함수
void main()
{
 upClass *ptr;                       //aClass를 부를지, bClass를 부를지 모르니까 둘의 공통 분모 클래스의 포인터 생성
 int num = 100;                     //적당한 조건문
//조건에 따라서
 if(num < 10){
  ptr = new aClass();           //aClass를 생성
 }else{
  ptr = new bClass();           //bClass를 생성
 }
 ptr->print();                       //위 조건에 따라 분기된 클래스의 print()함수를 실행
 delete ptr;
}

이렇게 하면


이런 결과가 나옵니다.
분명히 조건에 따라 bClass를 만들었는데 bClass의 print에서 정의한 cout << "bClass" << endl; 의 결과가 나오지 않습니다.

이런 문제를 해결하기 위해 쓰는것이 virtual 키워드 입니다.
컴파일러에게, 만약 이녀석 함수가 불러질때, 상속된 녀석이 이 함수를 오버라이딩 했을 가능성이 있으니 상속된 함수라면
정말 오버라이딩 했는지 확인한뒤 처리 하란 뜻입니다.

즉, 위의 상위 클래스에서 virtual를 추가하고 다시 컴파일 해보면

/************************************************************************/
/*상위클래스                                                            */
/************************************************************************/
class upClass
{
public:
  virtual void print(){
  cout<<"up class"<<endl;
 }
};

 


의도한 대로 bClass에서 생성된 print 가 실행 되었습니다.

추가로, 가끔 소멸자에 virtual 키워드를 붙이라고 하는걸 들어본 적이 있을껍니다.
그 이유는 위와 같이, 소멸자에 virtual 를 붙이지 않고 단순히

upClass *a = new bClass 로 할때

a가 소멸할시 소멸자는 upClass의 소멸자를 호출하는거지 , new 로 선언한 bClass 의 소멸자가 불러오지 않기 때문이죠.
virtual 를 소멸자에 선언하면, 당연히 bClass 의 소멸자가 실행되고 upClass의 소멸자가 실행됩니다.

이게 중요한 이유는 의도치 않는 메모리 누수가 발생하기 떄문이죠.
특히 서버 프로그래밍경우 메모리 관리가 엄청 중요합니다. (이것때문에 서버가 크리티컬로 다운되면 게임 전체가 stop이죠... ㅠㅠ)

쉽게 말해 코드상 디버그를 좀더 편하게 해주는 컴파일러 매크로임
그중 제일 잘 쓰는건 __FILE__ (현재 파일 이름), __FUNCTION__ (현재 함수 이름), __LINE__ (현재 실행 라인)
정도.
이하 msdn 을 긁어왔다.
http://msdn.microsoft.com/ko-kr/library/b0084kay(v=VS.90)

Names the predefined ANSI C and Microsoft C++ implementation macros.

The compiler recognizes predefined ANSI C macros and the Microsoft C++ implementation provides several more. These macros take no arguments and cannot be redefined. Some of the predefined macros listed below are defined with multiple values. See the following tables for more information.

ANSI-Compliant Predefined Macros

Macro

Description

__DATE__

The compilation date of the current source file. The date is a string literal of the form Mmm dd yyyy. The month name Mmm is the same as for dates generated by the library function asctime declared in TIME.H.

__FILE__

The name of the current source file. __FILE__ expands to a string surrounded by double quotation marks. To ensure that the full path to the file is displayed, use /FC (Full Path of Source Code File in Diagnostics).

You can create your own wide string version of __FILE__ as follows:

#include <stdio.h>
#define WIDEN2(x) L ## x
#define WIDEN(x) WIDEN2(x)
#define __WFILE__ WIDEN(__FILE__)
wchar_t *pwsz = __WFILE__;

int main() {}

__LINE__

The line number in the current source file. The line number is a decimal integer constant. It can be altered with a #line directive.

__STDC__

Indicates full conformance with the ANSI C standard. Defined as the integer constant 1 only if the /Za compiler option is given and you are not compiling C++ code; otherwise is undefined.

__TIME__

The most recent compilation time of the current source file. The time is a string literal of the form hh:mm:ss.

__TIMESTAMP__

The date and time of the last modification of the current source file, expressed as a string literal in the form Ddd Mmm Date hh:mm:ss yyyy, where Ddd is the abbreviated day of the week and Date is an integer from 1 to 31.

밑의 인터넷에서 가져온 소스를 테스트 해봤지만, 몇몇 컴파일러에선 args... 이후 뭐냐고 에러를 뱉기도 합니다.
그럴경우엔

#define DEBUG_ON       (TRUE)

#if DEBUG_ON
   // 일반 printf처럼
  #define debugMes(fmt, ...) printf(fmt, __VA_ARGS__)
   // 이 메시지가 나올땐, 소스 파일의 이름, 함수, 라인수를 같이 출력
  #define debugInfoMes(fmt, ...) printf( "[%s %s %d]" fmt, __FILE__, __FUNCTION__,__LINE__, __VA_ARGS__ )
#else
  #define debugMes(fmt, ...)
  #define debugInfoMes(fmt, ...)
#endif

식으로 매크로를 써주면 왠만한곳은 다 먹힙니다.


이하 긇어온곳 원본
/* -------------------------------------------------------
Printf를 이용한 debug
-------------------------------------------------------
2005.12.08 hugman@postech.ac.kr
원본출처 : http://cafe.naver.com/drv.cafe?iframe_url=/ArticleRead.nhn%3Farticleid=25

다음과 같은 좋은 debug 방법이 있습니다.
프로그램은 매우 간단하므로 한번 살펴보시면 쉽게 파악
하실 수 있습니다.
팁이 있다면,
      1) 프로그램을 다 완성하고, 이 debug를 아예 없애고 싶다면
         공통 해더파일에
          #define NDEBUG
          라고 해주면, 컴파일시 해당부분이 없어지게 됩니다.
      2) 컴파일 옵션으로 지울수도..
            컴파일 옵션에 -DNDEBUG 라고 추가합니다.
            예를 들어
            CFLAGS += -Wall -O2 -g
            라고 되어 있는 부분을
            CFLAGS += -Wall -O2 -g -DNDEBUG
            이렇게 말이죠.

  ++ 추가할점
  VC++ 에서도 잘 작동하는지는 확인해봐야 합니다.
-------------------------------------------------------
*/

#include "iostream"
//#define NDEBUG
//printf( "%s %s %d ", __FILE__, __FUNCTION__, __LINE__ );
// --> source 파일 이름, function 이름, line 번호를 출력해줍니다.

#ifndef NDEBUG
  #define dp(fmt,args...) printf( fmt, ## args )
  #define dlp(fmt,args...) printf( "[%s %s %d]" fmt, __FILE__, __FUNCTION__,__LINE__, ## args )
#else
  #define dp(fmt,args...)
  #define dlp(fmt,args...)
#endif

int func2()
{
  dlp(" ");
}
int main()
{
  dlp(" ");
  func2();
  func2();
}

설마 어셈블러까지 건드리겠어 라고 생각하시는 분들이 많으시겠지만,
실무에서 타사랑 경쟁하고 있다면 어셈블러를 쓰는게 더 낳은경우가 많습니다. (아무래도 기계 성능을 100%활용하니..)

문제는 C언어의 #if, #ifdef 같은걸 어셈에 쓰기가 애매하죠. 자료도 그닥 없고.. ㅠㅠ
gcc경우
$gcc -D DEBUG_WINDOWS=1 ...

하면 #if DEBUG_WINDOWS부분이 활성화 되서 이쪽만 소스가 컴파일 되지만
어셈도 그짓을 할려면 조금 특수한 방법을 써야 합니다.

우선 어셈 소스중 분기하고 싶은곳의 맨앞에
.ifdef DEBUG_WINDOWS

를 추가하고, 끝의 머리엔
.endif

를 추가합니다.

다음 컴파일 할땐
$gcc -c -x assembler-with-cpp -pipe -Wa,--defsym=DEBUG_WINDOWS=0 ...
식으로 컴파일 하면 됩니다.

빨간부분은 띄어쓰기 하면 안되고, 심볼을 넘길땐 = 기호가 없으면 안됩니다.
의미는
as에 심볼을 넘겨라 인데.
Wa가 as에 파라메터를 넘기는 옵션이고
--defsym은 심볼을 지정하는 옵션입니다.
심볼을 넘길땐, 심볼이름=데이터 를 넘기지만, 여기선 데이터엔 의미가 없으니 그냥 0을 썻습니다.

즉, 위의 어셈 소스는, 심볼이 선언되었다면 실행....

그보다... 울나란 어셈블러 자료가 너무 없는거 같은... ㅠㅠ

+ Recent posts