본문 바로가기

デベロップメント/ゲームプログラミング

1.4.2 60초만에 이해하는 C++

1.4.2 60초만에 이해하는 C++


일단은, 오브젝트 지향 프로그래밍의 기법을 매우 간단하게 설명합니다.

여기서는 오브젝트 지향 프로그래밍(OOP)을 자세히 설명하는 것은 하지 않습니다.


C++에서는 클래스는 1개의 선언을 말합니다.

그 클래스 선언부터 변수가 작성되고, 그것은 오브젝트라고 불립니다.

클래스는 private 또는 public으로써 선언되는 섹션을 포함하는 것이 가능합니다.

일반적으로 클래스의 private 섹션에서는 변수를 정의합니다.

한편, public 섹션에는 함수 프로토타입이나 완전한 함수를 기술합니다.

코드 1.1에 있는 것처럼, 본 책에서는 1개의 클래스에 헤더 파일과 소스 파일, 즉 2개의 파일을 사용합니다.


헤더파일에는 변수나 프로토타입 같은 것을 포함하며, 클래스 선언을 기술합니다.

경우에 따라서는 헤더 파일 내에 완전한 함수를 기술하는 것도 가능합니다.

이와 같은 함수는 디폴트로 인라인 함수처럼 되기 때문에 함수는 매우 작은 경우에 방법을 취합니다.


※ 컴파일러는 인라인 함수가 호출될 때에, 그 함수의 코드 전체를 프로그램에 그대로 삽입합니다.

그것 때문에 인라인 함수는 일반적인 함수에 비해 적지만, 빠르게 호출 할 수 있습니다.

그 속도화의 대상으로써, 실행 파일의 크기가 커집니다.


소스 파일에는 그 외의 함수의 전부를 구현한 코드를 기술합니다.

예를 들어 코드 1.1을 봅시다.

이것은 graphics.h의 일부로써, Graphics 클래스를 정의하고 있습니다.


#코드 1.1 Graphics 클래스의 정의


class Graphics{
private:
// DirectX 포인터 등
LP_3D direct3d;
LP_3DDEVICE device3d;
D3DPRESENT_PARAMETERS d3dpp;

// 그 외의 함수
HRESULT result;
HWND hwnd;
bool fullscreen;
int width;
int height;

// (엔진의 사용시에 내부적으로 사용합니다.
// 내부에는 유저가 사용하는 것은 없습니다.)
// D3D 프레젠테이션 파라메터를 초기화
void initD3Dpp();

public:
// 생성자
Graphics();
// 소멸자
virtual ~Graphics();
// direct3d와 device3d를 해방
void releaseAll();


// DirectX 그래픽을 초기화
// hw = 윈도우에의 핸들
// width = 넓이 (픽셀 단위)
// height = 높이 (픽셀 단위)
// fullscreen = 전체 화면의 경우는 true, 윈도우의 경우에는 false
// 에러시에는 GameError를 Throw

void initialize(HWND hw, int width, int height, bool fullscreen);

//오프 스크린 백 버퍼를 화면에 표시
HRESULT showBackbuffer();
};


※ 헤더파일에서는 각 함수 프로토타입 상에 자세한 코멘트를 기술하고 있는 것을 주목해주세요.

Visual C++에서는 이 코멘트가 코드 보완 윈도우에 표시됩니다.(그림 1.1을 참고)


#그림 1.1 코드 보완 윈도우


오브젝트 지향 프로그래밍에서는 오브젝트의 public 섹션만이 외부의 오브젝트부터 액세스 가능합니다.

이것은, 오브젝트 지향 프로그래밍을 처음 배우는 사람이 먼저 이해해야 할 중요한 개념 중 한개 입니다.

코드부터 사용할 수 있는 오브젝트의 public 부분뿐이며 통상, 그 부분에는 함수뿐만이 포함되어 있습니다.

즉, 오브젝트 내부에 포함되어있는 변수에 직접적이게 액세스하는 것은 아닙니다.

오브젝트 내의 변수를 조작하고 싶은 경우는 오브젝트의 변수를 사용하는 것에 대해서 그 조작을 실행합니다.


※ 오브젝트 내의 변수에 대한 액세스를 제한하는 그 개념은, 「데이터 은폐」 또는「정보 은폐」이라고 불리고 있습니다.


C++ 오브젝트에는 「자동」함수도 있습니다.
생성자 함수는 새로운 오브젝트가 작성 될 때에 자동으로 호출됩니다.
이 함수를 사용해서 오브젝트의 변수를 초기화합니다.
소멸자 함수는 오브젝트가 삭제될 때에 자동으로 호출됩니다.
이 함수를 사용해서 오브젝트가 예약되어있던 메모리를 해방합니다.
생성자 함수와 소멸자 함수는 2개의 기준을 채울 필요가 있습니다.
1개는 함수명이 클래스명과 같은 것으로, 남은 1개는 반환 값이 없는 것(void형도 아님)입니다.

코드 1.1에 보이는 Graphics 클래스에는 생성자 함수와 소멸자 함수는 다음과 같이 선언되어 있습니다.

// 생성자
Graphics();
// 소멸자
virtual ~Gaphics();


소멸자에는 물결(~)의 마크가 붙어있습니다.
이 클래스를 보다 고도한 클래스를 구축하기 위해서의 기본 클래스로써 사용하는 것을 허가한 경우에는, 키워드 virtual을 사용합니다.

일반적으로는, 헤더파일에 기술한 것은 함수 프로토타입밖에 없습니다.
남은 함수 코드는 소스파일에 기술되어 있습니다.
특정의 클래스에 포함된 함수를 보임에는 다음과 같이 「클래스명::」을 앞에 붙입니다.

bool Graphics::initialize(HWND hw, int w, int h, bool full){


プレフィックス(접두사)「Graphics::」에 따라, initialize 함수의 프로토타입이 Graphics 클래스 내에 있다고 하는 것이 컴파일러에 전해집니다.

※ ::은 스코프 해결 연산자입니다.


이 프로그램 내의 오브젝트를 사용하는데에는 오브젝트 변수를 직접 작성하, 오브젝트 포인터를 작성합니다.
Grphics 클래스에서 graphics라고 하는 이름의 오브젝트를 작성하는데에는 다음과 같습니다.

// Graphics 오브젝트
Graphics graphics;

이 후, 공개 함수에 액세스하는데에는 도트 연산자를 사용합니다.
이것은 구조체의 멤버에 액세스하는 경우와 같습니다.

// Graphics를 초기화
graphics.initialize(hwnd, WINDOW_WIDTH, WINDOW_HEIGHT, FULLSCREEN);


오브젝트에 대한 포인터를 사용하는 경우는 다음과 같습니다.

// Graphics 포인터
Graphics *graphics;


이 후, 다음과 같이 new를 사용해서 오브젝트를 작성합니다.


// Graphics 오브젝트를 작성
graphics = new Graphics;


오브젝트가 작성되었다면, 오브젝트 포인터와 -> 연산자를 사용해서 공개함수에 액세스 할 수 있습니다.


// Graphics 초기화
graphics->initialize(hwnd, WINDOW_WIDTH, WINDOW_HEIGHT, FULLSCREEN);


경우에 따라서 함수를 클래스의 안에 선언해도, 그 시점에서는 함수의 구현 내부를 알 수 없는 경우가 있습니다.

예를 들어 어떤 프로그래머도 화면상에 게임 아이템을 그릴 필요가 있으므로, 당연히 Game클래스에는 그를 위한 함수가 들어 있을 것입니다.

하지만 이후 개발된 가능성 있는 모든 게임에 대해서 게임 아이템의 묘화방법을 아는 것은 불가능합니다.

이 문제의 해결책이 되는 것이 순수가상함수의 사용입니다. 순수가상함수는, 다음과 같이 함수 프로토타입을 0으로 설정하는 것에 대해 선언합니다.


virtual void render() = 0;


이 선언이 Game클래스 안에 있는 경우, render 함수의 구현은 Game을 계승한 각 클래스에 제공할 필요가 있습니다.

다시 말해, 순수가상함수는 함수의 플레이스 홀더로써 기능합니다.


클래스 선언에서는 기존의 클래스를 계승하는 것이 가능합니다.

기존의 클래스를 계승하면, 기존 클래스의 모든것을 새로 복사하는 것이 됩니다.

Game 이라고 하는 이름의 클래스가 있고, 그 Game 클래스를 계승한 새로운 클래스 Spacewar을 작성하는 경우는, 다음과 같이 코딩합니다.


class Spacewar : public Game{
	// 새로운 Spacewar의 중심을 여기에 기술
}


「: public Game」 이라는 기술(記述)은, Spacewar 클래스가 Game 클래스를 계승한 것을 보여주고 있습니다.

Spacewar 클래스에는 Game 내에 있는 모든 것이 처음부터 포함되어 있습니다.

Spacewar 클래스에서는 그 코드에 대해, 추가, 삭제, 변경을 행하는 것이 가능합니다.


C++에 대한 마지막 설명은, 프로그램의 실행시에 발생하는 가능성 있는 예외 처리에 대해서입니다.

이의 처리를 행하기 위해서는, 메서드를 try/catch 블럭으로 감싸줍니다.

try/catch 블럭 내에서 발생하는 예외는 전부, 코드 내의 로컬에 처리됩니다.

프로그램을 정지시키는 원인이 되는 것은 없습니다.

예를 들어, 코드 1.2에 WinMain 함수를 표시합니다.


코드 1.2 WinMain에 대한 try/catch의 추가

try {
	game->initialize(hwnd); // GameError를 Throw
	// 메인의 메세지 루프
	int done = 0;
	while (!done) {
		if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
			//  종료 메세지를 출력
			if (msg.message == WM_QUIT) done = 1;
			// 메세지를 디코드 해서 WinProc에 건네줌
			TranslateMessage(&msg);
			DispatchMessage(&msg);
		}
		else game->run(hwnd); // 게임 루프를 실행
	}
	SAFE_DELETE(game); // 종료 전에 메모리를 해방
	return msg.wParam;
}
catch (const GameError &err) {
	game->deleteAll();
	DestroyWindow(hwnd);
	MessageBox(NULL, err.getMessage(), "Error", MB_OK);
}
catch (...) {
	game->DELETEAll();
	DestroyWindow(hwnd);
	MessageBox(NULL, "Unknown error occurred in game", "Error", MB_OK);
}


try{}의 안에, 예외 처리가 필요한 코드를 기술합니다.

이 예로는, 2번째의 catch 상태가 사용되고 있습니다.

처음의 catch(const GameError &err)는, GameError 예외만을 감지하는, 그 외는 전부 무시합니다.

2번째의 catch(...)는, 발생하는 모든 예외를 캐치합니다.

예외가 발생할 때에 실행하는 코드는 catch{}의 안에 기술되어 있습니다.


독자 예외를 작성하는 것도 가능합니다.

이 경우는, C++의 throw 상태를 사용합니다.

throw 구문은 「throw(item);」입니다.


Throw하는 아이템에는 거의 어떤 것이라도 사용할 수 있습니다.

이 코드에는 GameError 클래스를 작성한 것을 Throw 합니다.

독자 클래스를 작성하는 것에 대해 에러의 상황에 대해서 전하고 싶은 모든 정보를 담는 것이 가능합니다.

GameError 클래스의 코드를 코드 1.3에 보여드립니다.


이 클래스의 데이터 멤버는 int errorCode와 std::string message입니다.

errorCode 값의 후보는 이름 공간 gameErrorNS에 정의되어 있습니다.

이 클래스의 기본 생성자는 2개의 멤버 변수에 기본의 에러 코드 FATAL_ERROR과, 「Undefined Error in game」이라고 기본의 문자열을 설정합니다.

2개의 인수를 가진 생성자에는, 그 인수에 대해 errorCode와 message에 별개의 값을 지정하는 것이 가능합니다.


코드 1.3 GameError 클래스

//Programming 2D Games
// Copyright (c) 2011 by
// Charles Kelly
// gameError.h v1.0
// 게임엔진에 대해 Throw된 Error 클래스
#ifndef _GAMEERROR_H // 이 파일이 복수의 개소로 포함된 경우에
#define _GAMEERROR_H // 다중으로 정의되는 것을 막습니다.
#define WIN32_LEAN_AND_MEAN
#include 
#include 
namespace gameErrorNS {
	// 에러 코드
	// 음수는 게임을 셧다운할 필요가 있는 치명적인 에러를 보여줍니다.
	// 양수는 게임을 셧다운 할 필요가 없는 경고를 보여줍니다.
	const int FATAL_ERROR = -1;
	const int WARNING = 1;
}
// Game Error 클래스. 게임 엔진에 대해서 에러가 감지된 때에 Throw 됩니다.
// std::exception을 계승
class GameError : public std::exception {
private:
	int errorCode;
	std::string message;
public:
	// 기본 생성자
	GameError() throw() :errorCode(gameErrorNS::FATAL_ERROR),
		message("Undefined Error in game."){}
	// 생성자 복사
	GameError(const GameError& e) throw(): std::exception(e),
		errorCode(e.errorCode), message(e.message){}
	// 인수 있는 생성자
	GameErrorint(int code, const std::string &s) throw() :errorCode(code),
		message(s){}
	// 대입연산자
	GameERror& operator=(const GameError& rhs) throw() {
		std::exception::operator=(rhs);
		this->errorCode = rhs.errorCode;
		this->message = rhs.message;
	}
	// 소멸자
	virtual ~GameRror() throw() {};
	// 기본 클래스에 대한 오버 라이트
	virtual const char* what() const throw() {
		return this->getMessage();
	}
	const char* getMessage() const throw() { return message.c_str(); }
	int getErrorCode() const throw() { return errorCode; }
};
#endif


게임을 중단되게 하는 중요한 에러를 감지하는 경우는, 다음과 같이 GameError 클래스를 파라미터 errorCode 또는 message를 지정하여 Throw합니다.


if (FAILED(result))
throw(GameError(gameErrorNS::FATAL_ERROR,
	"Error creating Direct3D device"));


GameError 클래스는 표준의 예외 클래스 std::exception을 계승하고 있습니다.

표준 라이브러리에 대해서 Throw된 모든 예외는 std::exception에서 파생하였습니다.

std::exception을 캐치하는 것에 대해, 여기서 작성된 클래스도 포함하여, std::exception에서 파생한 모든 클래스를 캐치할 수 있습니다.



서장으로