Class란?
쉽게 설계도로 이해하면 편하다.
언리얼 엔진에서 동일한 Blue Print라 할지라도 Level에 배치할때마다 각각 다른 객체로 배치가 되는 것( == 동적할당)도 사실 설계도는 하나지만 그 설계도로 여러 건물을 지으면 모두 다른 건물이기 때문이다.
Class를 설계하는 방법
- 명사를 찾아서 정리(변수)
- 동사를 찾아서 정리(함수)
- 모두 영어로 변환 (명사 : Class, 동사 : Function )
- 만든 Class 안에 Fuction을 집어 넣는다.
- 추가로 필요한 것들을 생각하여 Class 안에 추가한다.
- 동일한 항목을 묶어서 부모 Class를 생성한다.
Class를 선언할 때 부모 클래스를 접근제한자와 같이 적어주면 해당 Class를 상속 받은 것이다.
Actor Player : public Pixel
{
public :
int X;
int Y;
}
부모 Class를 만들어서 상속하는 이유 :
멤버 변수로 해당 Class를 가져오는 것보다 수정이 훨씬 편리하다.
(해당 부모 Class 내용만 수정하면 상속 받은 모든 자식 객체에서 수정한 것과 같은 효과이다.)
언리얼 엔진에서는 Actor Class안에 위치, 회전, 크기라는 멤버 변수가 들어가 있기 때문에 Actor Class를 상속 받으면 기본적으로 위치, 크기, 회전 데이터를 갖으며 수정할 수있다.
즉, Actor Class를 상속 받지 않은 Class의 객체를 Level에 배치할 수 없는 것이다.
Class 설계와 관련하여 Has a 관계와 Is a 관계라는 것이 존재한다.
Has a : A가 B를 갖는다(멤버 변수)
is a : A는 B안에 포함된다.(상속 관계)
예를 들어 World has a Player 즉, Level(Scene)은 Player라는 객체를 멤버 변수로 갖는 것이다.
Apple is a Fruit 즉, 과일이라는 개념안에 사과라는 별개의 Class가 존재하므로 과일 Class가 부모, 사과 Class가 자식이 되는 것이다.
struct Collision
{
Actor* HitActor;
int Demage;
}
위와 같은 코드를 보자.
새로운 구조체를 선언하였고 그 안에 Actor Class의 객체를 동적으로 할당할 수 있는 Actor 포인터형 변수가 멤버 변수로 존재한다.
Class 안에서 선언된 멤버 변수는 Property, 멤버 함수는 Method라고 한다.
어디까지나 Class는 설계도이기 때문에 실제로 우리가 객체를 생성해야지 그 데이터를 담은 객체가 메모리를 잡는 것이다.
Class를 만들었다고 해서 메모리를 할당하는 게 아니다.
Class를 만들다보면 보통 Header에서는 변수와 함수의 선언을 CPP에서 함수의 내용을 구현하는데 여러명이서 작업을 하는 경우(분할구현, 협업)와 다른 개발자들에게 전달해주는 것은 Header만 전달해주기 때문에(개인 기술) 이렇게 진행한다.
Cpp에 구현한 내용은 개발자의 자산이라고 생각하면 된다.
void Actor::Move()
{
}
Cpp에서 함수 내부를 구현할때는 위와 같이 어떤 Class의 함수인지 Class명과 함께 전체 이름을 기재해야한다.
그렇다면 Class의 size는 어떻게 될까?
Class의 size는 오직 멤버 변수의 크기로만 정해진다.
함수는 객체별로 어차피 사용되는 구문이 모두 동일하기 때문에 실제로 함수가 객체별로 구현되는 것이 아니라 외부에 함수를 구현에 놓고 거기에 객체의 데이터만 가지고 가는 것 뿐이다.
쉽게 생각하면 함수가 있는 주소를 가지고 있다가 함수를 호출하면 알려준다고 보면 된다.
언리얼에서 엔진 class를 생성하는 경우 class 앞에 U가 붙는다.
Override
Class를 배우다보면 나오는 개념이다.
흔히 Overloading과 Override을 헷깔리곤 한다.
Override는 부모에 선언된 함수를 자녀에서 재정의할 때 사용하는 것이다.
예시를 들자면 모든 과일은 시간이 지나면 썩고 벌레가 꼬인다.
과일 Class에 Death 함수를 선언하고 내용은 "썩고 벌레가 꼬인다"로 작성했다고 해보자, 이때 유전자 변이로 만든 포도는 썩고 벌레가 꼬이는게 아니라 시간이 지나면서 점점 작아져서 결국 없어지게 되는 과일이다.
그러면 우리는 아래와 같이 할 수 있다.
virtual void Death();
부모에서 해당 함수 앞에 Virtual 키워드를 붙여 해당 함수가 자녀 클래스에서 재정의 되어있을 수도 있다고 알린다.
void Death() override;
자녀 클래스에서는 해당 함수를 선언하고 뒤에 override 키워드를 붙여 재정의했음을 알려주고 Death()함수를 정의하면 된다.
이러면 우리가 만든 유전자 변이 포도로 객체를 만들고 해당 객체의 Death를 호출하면 우리가 자녀 클래스에서 재정의한 내용의 함수가 실행되게 된다.
overloading은 받는 인자의 수, 자료형 등이 다른 경우 함수명이 같아도 다른 함수로 인식한다는 것이다.
void Function();
void Function();
이처럼 동일한 이름의 함수명은 두번 사용할 수 없다.
void function(int a);
void function(bool b);
그러나 이렇게 인자가 다른 경우는 서로 다른 함수로 인식하기 때문에 사용이 가능해진다.
실제로 동작시 컴파일러가 해당 함수명에 다른 글자를 붙여서 다른 함수로 인식하게 되는 것이다.
다른 글자를 붙이는 것을 Name Mangling이라고 한다
함수는 인자 옆에 = 0;, = 10; 등 값을 넣어주면 인자가 없는 경우 설정될 기본 값을 정할 수 있다.
Template(*Meta Programming)
우리가 숫자를 서로 더하는 함수를 만들었을때, 인자로 받는 자료형 하나하나 오버로딩을 해서 함수를 만들어야한다.
이 귀찮음을 해결하기 위해 템플릿이 추가되었는데 쉽게 말해 함수에 구멍을 뚫어 놓는 것과 같다.
그래서 우리가 int, float라고 하면 정해진 위치에 int와 float를 넣는 것이고 int, int라면 그 자리에 int와 int를 넣는 것이다.
template<typename T>
T Add(T A, T B)
{
return A + B;
}
Add<int>(3, 7);
위 코드에서 Add<float>로 사용하면 함수의 T자리에 Float가 들어가서 계산이 된다.
템플릿에서는 typename T, typename S 등 typename을 추가할 수도 있다.
(template<class T, class Y>도 가능, 단 이때 class는 그 클래스가 아니라 그냥 typename 대신에 쓴 단어일 뿐이다.)
Class도 Template으로 만들 수 있다.
template<typename T>
Class Actor
{
public :
T X;
T Y;
T Z;
}
Template을 사용하면 Compile 시 해당 자료형에 맞게 코드를 생성해준다.
다만, 이럴 경우 Error가 발생해도 코드가 없기 때문에 정확한 오류를 찾는데 시간이 오래 걸릴 수 있다.
우리가 사용한 Vector도 Class Template으로 구현되어 있기 때문에 사용하기 위해선 Vector<원하는 자료형> 변수명으로 선언하여야 한다.
Vector와 같이 데이터를 저장하는 것을 Container라고 하는데 자료구조의 일종이다.
Container : 라이브러리에서 제공하는 데이터 구조의 구현
Data Structure : 데이터를 저장하고 관리하는 방법
Vector는 Size가 따로 없어 Data를 넣을때 Size가 생긴다.
우리가 Actor class의 객체를 만들어 각각의 데이터를 조절한다고 해보자.
Actor* Player = new Actor[10];
Player[0].Move();
Player[0].Move();
Player[1].Move();
Player[1].Move();
Player[2].Move();
Player[2].Move();
Player[1].Move();
너무나 귀찮고 코드가 길어진다.
대신에 Vector를 쓰면 훨씬 짧고 간결해진다.
vector<Monster*> MyMonster;
MyMonster.push_back(new Monster());
MyMonster.push_back(new Monster());
MyMonster.push_back(new Monster());
MyMonster.push_back(new Monster());
for(int i = 0; i < MyMonster.size(); ++i)
{
delete MyMonster[i];
}
MyMonster.clear();
기타
- 동적할당 하는 이유 : 성능( 필요할 때만 만들어지므로 그때만 resource를 잡아먹기때문이다.)
- 데이터 모델링(데이터로 표현하는 것 == 자료구조 == 테이블)
- Meta Programming : 컴파일 타임에 생성되는 코드로 프로그래밍 하는 것
- header에서 class를 전방선언했다면 반드시 CPP에서 include해주어야 한다.
'C++ > 문법' 카테고리의 다른 글
File Input Output & Delta Time (0) | 2024.11.07 |
---|---|
Inline & Switch case문 (8) | 2024.11.06 |
C++ 기본 문법(문자열, 동적할당, 구조체) (2) | 2024.10.25 |
C++ 기본 문법(변수와 함수) (0) | 2024.10.23 |
C++ 기본 문법(연산자, 배열) (1) | 2024.10.18 |