3 분 소요

4주차 Unreal/C++ 과제 5

언리얼 엔진 맛보기 과제

액터 클래스를 새로 생성하고
UE_LOG를 활용하여 로그 출력,
간단한 로직 구현을 통해 언리얼 엔진에 익숙해지기

  • 액터 생성
  • 기본 문법 활용
  • UE_LOG로 로그 출력

구현 기능

Image

  • actor가 spawn되는 시점에 동작되도록 코드 구현

클래스 설명과 기타 파일

  • 클래스
MyActor
 - 과제에서 요구하는 지점 이동, 로깅, 이벤트 체크 등에 대한 구현
  • 유틸 파일
LogTests
- 커스텀 Log 인 LogCoord 생성

MyUtils
- namespace와 ForceInline으로 묶은 기타 유틸리티 함수 묶음 파일

트러블 슈팅 - Utility 파일의 중복 선언?

Image

이상하게도 MyUtils에서
추가한 유틸용 함수에 ‘재정의’문제가 발생하였다고 한다

Image

흐음… 딱 보니 FORCEINLINE을 빼뜨려서 그런듯한데
(사실 이전 코드는 0~100의 RandomRange를 돌려서
FORCEINLINE을 사용하지 않았었다)

Image

FORCEINLINE을 넣으니 문제 없이 컴파일 되었다?

애초에 이건 왜 발생한 문제일까?

LNK 2005

‘기호’(심벌)이 이미 ‘정의’된 경우 발생하는 링킹 에러
(전처리->컴파일->어셈블->링킹 중 링킹 단계에서 발생)
(일반적으로 ‘재정의’ 관련 에러이다)

  • 링커가 ‘여러’ obj나 lib 를 합치며 ‘같은 이름’의 ‘함수/전역변수’ 발견시
    뱉어내는 오류

???
이게 왜 발생?
나는 분명 저 함수를 한번만 썼는데?

대략적인 흐름(정확하지 않을 수 있음)

  • 전역 함수 (myUtils.h) 존재
  • 그런데 사용하는 myactor.cpp에서 include 하면서
    전처리가 ‘이를 복붙’함

  • 나중에 링커가 봤는데
    • 어라? myUtils.h랑 myactor.cpp에서 둘다 같은 함수 이름을 사용하네?
    • lnk 2005 퉷!
      (참고로 다른 h에서 include해도 lnk2005가 발생하는 것은 여전한
      ‘같은 컴파일 단위’에서 중복이 발견될때 컴파일러가 잡아줄 수 있음)

FORCEINLINE이 해결하는 이유?

ODR(단일 정의 규칙) : 하나의 함수/변수 는 프로그램에 한번 정의되어야 함
(inline 변수는 C++17에 등장)

inline 속성은 해당 조건에 대한 ‘특례’로서 중복 선언을 허용한다

  • inline 함수?
    ‘함수 정의를 호출 지점에 그대로 삽입하는 것을 컴파일러에게 제안’하도록
    설정하는 옵션
    (오버헤드 감소)
    (다만, 반드시 inline 되지는 않음을 유의할 것)
  • inline 변수(C++ 17)
    여러 파일에서 #include 한 변수가 중복 정의되지 않도록 링커에게 알려주는 역할
    특정 헤더에서 선언한 전역 변수 등을 중복 선언하지 않도록 설정
    (inline 선언을 추가하면 클래스 / 구조체의
    static 정적 멤버를 cpp에서 재정의 하지 않아도 된다)

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() 로 힙으로 변환시킬 수 있음

후기

예제 자체는 쉬웠기에 비교적 빠르게 작성할 수 있었다
지금 더 만져볼지, 아니면 다른 공부를 하며
다음 과제를 신경쓸지 고민해보는 중이다

댓글남기기