4주차 Unreal/C++ 과제 5
4주차 Unreal/C++ 과제 5
언리얼 엔진 맛보기 과제
액터 클래스를 새로 생성하고
UE_LOG를 활용하여 로그 출력,
간단한 로직 구현을 통해 언리얼 엔진에 익숙해지기
- 액터 생성
- 기본 문법 활용
- UE_LOG로 로그 출력
구현 기능
- actor가 spawn되는 시점에 동작되도록 코드 구현
클래스 설명과 기타 파일
- 클래스
MyActor
- 과제에서 요구하는 지점 이동, 로깅, 이벤트 체크 등에 대한 구현
- 유틸 파일
LogTests
- 커스텀 Log 인 LogCoord 생성
MyUtils
- namespace와 ForceInline으로 묶은 기타 유틸리티 함수 묶음 파일
트러블 슈팅 - Utility 파일의 중복 선언?
이상하게도 MyUtils에서
추가한 유틸용 함수에 ‘재정의’문제가 발생하였다고 한다
흐음… 딱 보니 FORCEINLINE을 빼뜨려서 그런듯한데
(사실 이전 코드는 0~100의 RandomRange를 돌려서
FORCEINLINE을 사용하지 않았었다)
FORCEINLINE을 넣으니 문제 없이 컴파일 되었다?
애초에 이건 왜 발생한 문제일까?
LNK 2005
‘기호’(심벌)이 이미 ‘정의’된 경우 발생하는 링킹 에러
(전처리->컴파일->어셈블->링킹 중 링킹 단계에서 발생)
(일반적으로 ‘재정의’ 관련 에러이다)
- 링커가 ‘여러’ obj나 lib 를 합치며 ‘같은 이름’의 ‘함수/전역변수’ 발견시
뱉어내는 오류
???
이게 왜 발생?
나는 분명 저 함수를 한번만 썼는데?
대략적인 흐름(정확하지 않을 수 있음)
- 전역 함수 (myUtils.h) 존재
-
그런데 사용하는 myactor.cpp에서 include 하면서
전처리가 ‘이를 복붙’함 - 나중에 링커가 봤는데
- 어라? myUtils.h랑 myactor.cpp에서 둘다 같은 함수 이름을 사용하네?
- lnk 2005 퉷!
(참고로 다른 h에서 include해도 lnk2005가 발생하는 것은 여전한
‘같은 컴파일 단위’에서 중복이 발견될때 컴파일러가 잡아줄 수 있음)
- 어라? myUtils.h랑 myactor.cpp에서 둘다 같은 함수 이름을 사용하네?
FORCEINLINE이 해결하는 이유?
ODR(단일 정의 규칙) : 하나의 함수/변수 는 프로그램에 한번 정의되어야 함
(inline 변수는 C++17에 등장)
inline 속성은 해당 조건에 대한 ‘특례’로서 중복 선언을 허용한다
-
- inline 함수?
- ‘함수 정의를 호출 지점에 그대로 삽입하는 것을 컴파일러에게 제안’하도록
설정하는 옵션
(오버헤드 감소)
(다만, 반드시 inline 되지는 않음을 유의할 것)
- inline 함수?
-
- inline 변수(C++ 17)
- 여러 파일에서 #include 한 변수가 중복 정의되지 않도록 링커에게 알려주는 역할
특정 헤더에서 선언한 전역 변수 등을 중복 선언하지 않도록 설정
(inline 선언을 추가하면 클래스 / 구조체의
static 정적 멤버를 cpp에서 재정의 하지 않아도 된다)
- inline 변수(C++ 17)
LNK 2005 를 피하려면
패턴 | 위치 | 링크 에러? | 비고 |
---|---|---|---|
전역 함수, inline 없음 |
헤더(여러 TU 포함) | ✅ LNK2005 | 중복 정의 발생 |
전역 함수, inline /FORCEINLINE |
헤더 | ❌ | ODR 특례로 허용 |
멤버 함수, 클래스 본문 안 정의 | 헤더 | ❌ | 암묵적 inline |
멤버 함수, 클래스 밖 정의(inline 없음) |
헤더 | ✅ LNK2005 | 바깥 정의는 inline 필요 |
- 암묵적으로 클래스 내부의 멤버 함수는 inline 함수 취급하기에 에러를 내뱉지 않음!
(그래서 Getter/Setter를 헤더에서 구현하더라도 에러가 없다!)
여담 : Template
- template 클래스는 ‘명시적 특수화’ 상황을 제외하면
ODR 예외를 가진다
(명시적 특수화는 inline을 붙여주거나
하나만 정의해야 함)
TMI : TArray
Unreal의 std::vector 라 생각
가변 크기 동적 배열 컨테이너
(리플렉션과 GC 시스템이 고려되었으니
언리얼에선 이걸 사용하자)
공식문서
- 생성 및 접근 방법
TArray<int32> Numbers;
Numbers.Add(10);
Numbers[0] = 5; // 인덱스 접근
int32 First = Numbers[0];
- 추가 / 삽입
Numbers.Add(30); // 끝에 추가
Numbers.Emplace(40); // 생성자 인자 전달 방식으로 추가
Numbers.Insert(50, 1); // 인덱스 1 위치에 삽입
- 삭제
Numbers.Remove(20); // 값 기준으로 삭제 (처음 매칭되는 요소)
Numbers.RemoveAt(0); // 인덱스 기준 삭제
Numbers.Empty(); // 전체 비우기
- 탐색
int32 Index = Numbers.Find(30); // 값 찾기
bool bContains = Numbers.Contains(50);
- 정렬 / 유틸리티
Numbers.Sort(); // 기본 오름차순 정렬
Numbers.Sort([](int32 A, int32 B) {
return A > B; // 사용자 정의 (예시는 내림차순)
});
Numbers.Num(); // 현재 크기
Numbers.Max(); // 내부 버퍼 크기
Numbers.SetNum(10); // 크기 강제 변경 (기본값 채움)
- 반복문
for (int32 Num : Numbers) { ... }
for (int32 i = 0; i < Numbers.Num(); i++) {
UE_LOG(LogTemp, Log, TEXT("%d"), Numbers[i]);
}
- 메모리 관련 함수
Numbers.Reserve(100); // 100개 용량 미리 확보
Numbers.Shrink(); // 실제 크기에 맞게 메모리 줄임
정리
기능 | 함수/매서드 | 설명 |
---|---|---|
추가 | Add() , Emplace() , Insert() |
배열 끝이나 특정 위치에 요소 추가 |
삭제 | Remove() , RemoveAt() , Empty() |
값/인덱스/전체 삭제 |
탐색 | Find() , Contains() |
값 존재 여부, 인덱스 탐색 |
크기 조절 | Num() , SetNum() , Reserve() |
크기/용량 관리 |
정렬 | Sort() , StableSort() |
오름/내림차순 및 사용자 정의 |
메모리 최적화 | Shrink() , Max() |
버퍼 관리 |
접근 | [] , Last() , Top() |
인덱스, 마지막 요소 접근 |
- 이러한 기능 뿐 아니라
Heapify() 로 힙으로 변환시킬 수 있음
후기
예제 자체는 쉬웠기에 비교적 빠르게 작성할 수 있었다
지금 더 만져볼지, 아니면 다른 공부를 하며
다음 과제를 신경쓸지 고민해보는 중이다
댓글남기기