[Unreal] 언리얼 기초 C++_04
본 문서는 어소트락 언리얼엔진 게임프로그래머 양성과정의 강의를 토대로 필기한 내용입니다
static 멤버 변수
- 모든 객체가 한 메모리를 공유하는 멤버 변수
- DS에 할당
- static 멤버의 메모리는 클래스 객체가 생성될 때 생성되지 않고, 프로그램 시작 시에 생성되기 때문에 클래스의 메모리 크기에 영향을 주지 않음
- 객체를 여러 개 생성하더라도 정적 멤버는 하나만 존재
- 객체를 생성하지 않아도 사용 가능
- 클래스 안의 정적 변수
- 전역 범위에서
자료형이름 클래스이름::static변수이름 = 초기화할 값형식으로 초기화 필요
- 전역 범위에서
static 멤버 함수
- 객체와 독립적, 객체 생성과 상관 없음
- 멤버 변수는 객체가 생성되어야 메모리를 할당 받기 때문에 static 멤버 함수 내에서는 멤버 변수를 사용할 수 없다
- 전역에서 메모리가 할당되는 static 멤버 변수는 사용 가능
- CS에 할당
- static 멤버 함수는 객체들의 생성과 무관하며 언제 어디서든 클래스 이름으로도 접근이 가능해야 하기 때문에 static 멤버 함수 내부에서는 this 포인터를 사용 불가
// static 멤버 변수와 static 멤버 함수
#include <iostream>
using namespace std;
void TestStatic() {
static int Number = 100; // 정적 멤버 변수 DS에 할당된다
++Number;
cout << Number << endl;
}
class CStatic
{
public :
CStatic()
{
}
~CStatic()
{
}
public :
long long mA;
static int mB; // 선언만 가능, 전역 변수와는 다름, 클래스에 소속된 정적 멤버 변수
void Output()
{
cout << "A : " << mA << " B : " << mB << endl;
}
// 정적 멤버 함수는 CS에 할당된다
// static 멤버함수는 this가 없다
// static 멤버 함수 안에서는 static 멤버 변수와 static 멤버 함수만 사용 가능
static void OutputStatic()
{
// mA = 100; // 비정적 멤버참조는 특정 객체에 상대적이어야 한다 // (this->) 가 없음
mB = 200;
// this->Output(); // 멤버 변수 호출 불가
cout << mB << endl;
}
};
int CStatic::mB = 0; // 초기화를 클래스 외부(전역 범위)에서 진행
int main()
{
// static 키워드
TestStatic(); // 101
TestStatic(); // 102
TestStatic(); // 103
// static 멤버 변수
CStatic st1, st2, st3;
st1.mB = 500;
cout << sizeof(CStatic) << endl; // long long 타입 mA 8바이트만 CStatic 사이즈에 영향을 끼친다
st1.mA = 200;
st2.mA = 300;
st3.mA = 400;
st1.mB = 500;
st2.mB = 600;
st3.mB = 700;
CStatic::mB = 800; // 클래스안의 정적 멤버 변수 유일하기 때문에 CStatic:: 접근지정연산자로 접근 가능
st1.Output(); // A : 200 B : 800
st2.Output(); // A : 300 B : 800
st3.Output(); // A : 400 B : 800
// static 멤버 함수
st1.OutputStatic();
st3.Output(); // A : 400 B : 200
st1.OutputStatic();
st2.OutputStatic();
st3.OutputStatic();
CStatic::OutputStatic();
// 함수 포인터로 함수 실행하기 : static 멤버 함수
void (*Func)() = CStatic::OutputStatic;
Func();
// 함수 포인터로 함수 실행하기 : 일반 멤버 함수
// void(*Func1)() = &CStatic::Output; // Error!, 일반 멤버 함수는 안됨
void (CStatic:: * Func2)() = &CStatic::Output; // 일반 멤버 함수는 주소를 붙여줘야 한다
// Func2(); // Error! // 객체로 접근해야 한다
(st1.*Func2)(); // 멤버 함수 포인터는 객체의 내용물을 참조하기 때문에 (객체.*멤버함수포인터)(매개변수)로 사용해야 한다.
return 0;
}
멤버 함수 포인터와 static 멤버 함수
- 멤버 함수 포인터는
&클래스이름::함수이름으로 접근해야 한다 - 멤버 변수는 각 객체들마다 따로 메모리를 가져 주소가 다르지만
- 멤버 함수는 객체마다 함수 메모리를 따로 갖는 방식이 아니기 때문
- 멤버 함수는 어딘가 한군데에 저장되어 있고 각 객체마다 그 공간에 동일하게 접근하여 각자의 다른 데이터로 사용하는 방식이다.
- 따라서 일반 함수의 주소와는 다르게 멤버 함수의 주소를 받아와야 함
- 그럴려면 속해있는 클래스가 어디인지 알려주어야 한다.
&Something::
- 함수 포인터로 함수 실행하기
- 일반 멤버 함수
- 포인터 선언 시
&클래스이름::함수이름으로 선언해야 함 - 함수 포인터로 일반 멤버 함수를 실행할 때, 꼭
(객체.*멤버함수포인터)(매개변수)로 실행해야 함 - 함수 포인터는 객체에 종속되는 일반 멤버 함수를 참조하고 있기 때문에 단독으로 호출할 수 없기 때문
- 포인터로 객체를 참조하고 있다면
(객체->*멤버함수포인터)(매개변수)로 실행
- 포인터 선언 시
- static 멤버 함수
- 객체와 무관하게 연산이 이루어 짐
- 일반 함수 포인터로 취급 받는다
- 일반 멤버 함수
// 함수 포인터로 함수 실행하기 : 일반 멤버 함수
// void(*Func1)() = &CStatic::Output; // Error! // 일반 멤버 함수는 안됨
void (CStatic:: * Func2)() = &CStatic::Output; // 일반 멤버 함수는 주소를 붙여줘야 한다
// Func2(); // Error! // 객체로 접근해야 한다
(st1.*Func2)(); // 멤버 함수 포인터는 객체의 내용물을 참조하기 때문에 (객체.*멤버함수포인터)(매개변수)로 사용해야 한다.
// 함수 포인터로 함수 실행하기 : static 멤버 함수
void (*Func)() = CStatic::OutputStatic; // static 멤버 함수는 주소를 붙여주지 않아도 호출 가능
Func(); // output
- std::functional
#include <functional>
using namespace std;
// CStatic 클래스 생략 //
int main()
{
function<void()> Func3 = CStatic::OutputStatic;
Func3;
Func3 = bind(&CStatic::Output, st1); // 함수주소와, 객체를 묶어서 사용도 가능
Func3();
return 0;
}
friend
- 클래스에서 어떤 함수나 다른 클래스를 friend, 즉 친구로 지정하면 자신의 private 멤버들에도 자유롭게 접근할 수 있게 허락해 줌
- 클래스를 친구로 삼는 경우
class A { friend class B;}- A 라는 클래스에서 B 라는 클래스를 통째로 친구 삼으면 B 클래스의 모든 멤버 함수에서 A 의 private 멤버들에도 자유롭게 접근할 수 있게 됨
- 다른 클래스의 특정 멤버 함수만 친구로 삼고 싶을 때
friend void B::doSomething(A &a);
class CClass1
{
friend class CClass2;
private:
int mA;
};
class CClass2
{
private :
CClass1 mC;
public :
CClass2() {
mC.mA = 100;
}
~CClass2() {
}
};
싱글톤
- static 멤버를 이용해서 객체 생성을 컨트롤하는 디자인 패턴 중 하나
- 게임 상이나 메모리 상으로 단 하나만 존재하며 언제 어디서든 사용 가능한 오브젝트를 만들 때 사용되는 패턴
#include <iostream>
using namespace std;
class CSingleton
{
private:
CSingleton()
{
}
~CSingleton()
{
}
public:
void Output()
{
cout << "CSingleton Output Function" << endl;
}
private:
static CSingleton* mInst; // 동적 할당을 통해 할당하면 메모리 제어가 용이
public:
static CSingleton* GetInst()
{
if (nullptr == mInst)
{
mInst = new CSingleton;
}
return mInst;
}
static void DestroyInst()
{
if (nullptr != mInst)
{
delete mInst;
mInst = nullptr;
}
}
};
CSingleton* CSingleton::mInst = nullptr; // 포인터 변수 선언은 nullptr 할당과 동시에 진행하기
int main()
{
// CSingleton single1; // private 생성자, 소멸자의 경우 객체 생성이 불가능
CSingleton::GetInst()->Output();
CSingleton::GetInst()->Output();
CSingleton::GetInst()->Output();
CSingleton::DestroyInst();
return 0;
}
- 배열을 활용한 싱글톤 생성
#include <iostream>
using namespace std;
#define Singleton_Array_Count 10
class CSingletonArray
{
private:
CSingletonArray()
{
}
~CSingletonArray()
{
}
public:
void Output()
{
cout << "CSingleton Output Function" << endl;
}
private:
static CSingletonArray* mInst[Singleton_Array_Count]; // 포인터 변수 10개가 생성됨 (80바이트 만큼의 포인터변수 생성)
public:
static CSingletonArray* GetInst(int idx)
{
if (nullptr == mInst[idx])
{
mInst[idx] = new CSingletonArray;
}
return mInst[idx];
}
static void DestroyInst(int idx)
{
if (nullptr != mInst[idx])
{
delete mInst[idx];
mInst[idx] = nullptr;
}
}
static void DestroyAll()
{
for (int i = 0; i < Singleton_Array_Count; i++)
{
if (nullptr != mInst[i])
{
delete mInst[i];
mInst[i] = nullptr;
}
}
}
};
CSingletonArray* CSingletonArray::mInst[Singleton_Array_Count] = {};
int main()
{
CSingletonArray::GetInst(3);
CSingletonArray::GetInst(5);
CSingletonArray::DestroyInst(3); // 3번 인덱스에 해당하는 싱글톤 동적할당 해제
CSingletonArray::DestroyAll(); // 모두 할당 해제
return 0;
}
템플릿
- 타입을 원하는 타입으로 지정할 때 사용
- 템플릿은 컴파일 타임에 결정되는 것만 사용할 수 있다
함수 템플릿
- 클래스 템플릿은 그 자체로 클래스는 아님, 클래스의 틀일뿐
// 함수 템플릿 일반화
template <typename 원하는이름>
원하는이름 (매개변수)
{
함수내용
}
template <typename 원하는이름, typename 원하는이름2>
#include <iostream>
using namespace std;
// 함수 오버로딩
int Plus(int num1, int num2) { return num1 + num2; }
float Plus(float num1, float num2) { return num1 + num2; }
// 함수 템플릿 일반화
template <typename T>
T Minus(T num1, T num2)
{
return num1 - num2;
}
// default type 템플릿
template <typename T = int> // defalut type 지정 가능
void OutputTemplateDefaultType()
{
cout << typeid(T).name() << endl; // 타입명을 알기위한 함수
// typeid(T).hash_code() == typeid(int).hash_code();
}
// 비타입템플릿 인수를 사용하면 템플릿 매개변수(count)는 이 함수 내에서만 사용할 수 있는 상수가 됨
template <typename T = int, int count>
void TestintTemplate()
{
cout << count << endl;
int arr[count];
int arrCount = 10;
int arr1[arrCount];
}
int main()
{
cout << Plus(10, 20) << endl; // 30
cout << Plus(103.6f, 230.05f) << endl; // 333.65
cout << Minus<float>(3.14f, 230.112f) << endl; // -226.972
cout << Minus(10, 30) << endl; // -20 // 템플릿 함수에 타입을 지정안할 경우 전달인수로 들어간 값의 타입으로 자동 지정됨
TestintTemplate<int, 30>(); // 30 (상수)
TestintTemplate<int, 50>(); // 50 (상수)
return 0;
}
클래스 템플릿
- 보통 헤더파일에 생성 : 템플릿은 선언과 구현을 동시에 헤더에 생성 => 링킹에러 방지
- 비타입템플릿 인수를 사용하면 count는 이 함수 내에서만 사용할 수 있는 상수가 됨
- 일반변수는 컴파일 때 메모리가 정해지지 않음
- const 상수는 컴파일 때 메모리가 정해지기 때문
// 클래스 템플릿 일반화
template <class 클래스이름>
클래스이름 (매개변수)
{
함수내용
}
#include <iostream>
using namespace std;
template<class T, class T1>
void OutputTemplate(T first, T1 second)
{
}
// 템플릿 클래스
template <typename T>
class CTemplate
{
public:
T mA;
public:
void Test()
{
cout << "Test" << endl;
}
// 멤버함수에 템플릿타입을 따로 지정하는 것도 가능
template<typename FuncType1>
void Test1(T First, FuncType1 Second)
{
cout << typeid(T).name() << endl; // 클래스 선언할 때 지정한 타입
cout << typeid(FuncType1).name() << endl; // 함수 호출할 때 지정한 타입
}
};
int main()
{
OutputTemplate<int, float>(10, 3.14f);
CTemplate<int> tp1;
CTemplate<float> tp2;
tp1.mA = 100;
tp2.mA = 3.14f;
cout << tp1.mA << endl;
cout << tp2.mA << endl;
tp1.Test();
tp2.Test1<int>(3.14f, 20); // float, int
return 0;
}
헤더파일과 소스파일
- 함수의 선언부와 구현부를 나눠서 코딩
- add.h 헤더파일은 프로젝트와 동일한 위치에 있어야 한다.
- 헤더에서 헤더를 참조하는 것 -> 순환 참조 (Error)
- 전방선언을 헤더에 하고
- 포인터 변수로 포인터 객체 생성
- 구현부에서 사용할 헤더 include
- GameInfo.cpp ```cpp #include “GameInfo.h”
// 함수의 구현부 void ItemOutput(const FItem& item) { cout « “이름 : “ « item.Name « endl; }
- GameInfo.h
```cpp
#pragma once
#include <iostream>
using namespace std;
namespace EItemType
{
enum Type
{
Weapon,
Armor
};
}
struct FItem
{
char Name[32] = {};
EItemType::Type ItemType;
};
void ItemOutput(const FItem& item); // 함수의 선언부
- main ```cpp #include “GameInfo.h”
int main() { FItem item;
strcpy_s(item.Name, "목검");
ItemOutput(item);
return 0; } ```
- monster.h ```cpp #pragma once #include “GameInfo.h” // #include “Effect.h” // 순환참조 방지
class CEffect; // 전방선언
class CMonster { public: CEffect* mEffect; // 포인터 변수로 선언
public: CMonster(); // 헤더에는 선언만 존재 ~CMonster();
protected: char Name[32] = {}; int mAttack = 0; int mDefense = 0;
public: void Output(); void Output1(); void Output2(); };
- monster.cpp
```cpp
#include "Monster.h"
#include "Effect.h"
CMonster::CMonster() { }
CMonster::~CMonster() { }
void CMonster::Output() { }
void CMonster::Output1() { }
void CMonster::Output2() { }
- effect.h ```cpp #pragma once #include “GameInfo.h” //#include “Monster.h”
class CMonster; // 전방선언
class CEffect { public: CEffect(); ~CEffect();
public: CMonster* mMonster; // 포인터 변수로 포인터 객체 생성 };
- effect.cpp
```cpp
#include "Effect.h"
#include "Monster.h"
CEffect::CEffect()
{
mMonster = new CMonster;
mMonster->Output();
}
CEffect::~CEffect()
{
delete mMonster;
}