득이공간

[언리얼 C++의 이해] 2장. 언리얼 C++ 모던객체지향 설계 본문

GP/UE5

[언리얼 C++의 이해] 2장. 언리얼 C++ 모던객체지향 설계

쟁득 2024. 1. 29. 20:49
해당 게시물은 이득우 교수님의 '언리얼 C++의 이해' 강의를 수강하며
학습한 내용을 개인적으로 정리한 글입니다.

📌 목차 - 2장. 언리얼 C++ 모던객체지향 설계

2-1. 언리얼 C++ 설계 1 - 인터페이스
2-2. 언리얼 C++ 설계 2 - 컴포지션
2-3. 언리얼 C++ 설계 3 - 델리게이트


📌 2-1. 언리얼 C++ 설계 1 - 인터페이스

1. 클래스가 반드시 구현해야 하는 기능을 지정하는데 사용한다.
2. C++은 기본적으로 다중상속을 지원하지만, 언리얼 C+의 인터페이스를 사용해 가급적 축소된 다중상속의 형태로 구현하는 것이 향후 유지보수에 도움이 된다.
3. 언리얼 C++ 인터페이스는 두 개의 클래스를 생성한다.
4. 언리얼 C++ 인터페이스는 추상 타입으로 강제되지 않고, 내부에 기본 함수를 구현할 수 있다.

 

* 인터페이스
- 반드시 ~한 행동을 하면 이를 상속받는다.
- 다형성 구현, 의존성 분리
- U 타입 클래스: 인터페이스 구현 여부 파악
- I 타입 클래스: 인터페이스 구성 및 구현

* 에디터 - Refresh Visual Studio
- 폴더 검사 후 프로젝트 구성 변경

* class PROJECTNAME_API ClassName
- 프로젝트마다 설정을 확인한다.

* FORCEINLINE 인라인 함수 선언

* 추상 클래스 인터페이스, 순수가상함수 -> 하위 클래스 구현 강제
- 일반 가상함수 -> 하위 클래스 구현 x 무방

* 캐스팅을 통한 인터페이스 식별


📌 2-2. 언리얼 C++ 설계 2 - 컴포지션

1. 언리얼 C++는 컴포지션을 구현하는 독특한 패턴이 있다.
2. 클래스 기본 객체를 생성하는 생성자 코드를 사용해 복잡한 언리얼 오브젝트를 생성할 수 있다.
3. 언리얼 C++ 컴포지션의 Has-A 관계에 사용되는 용어
- 내가 소유한 하위 오브젝트: Subobject
- 나를 소유한 상위 오브젝트: Outer
4. 언리얼 C++가 제공하는 확장 열거형을 사용해 다양한 메타 정보를 넣고 활용할 수 있다.

 

* 컴포지션
- Has-A 관계를 구현하는 설계 방법

* 조합 방법
1: CDO에 미리 언리얼 오브젝트를 생성해 조합한다. (필수적 포함관계) // CreateDefaultSubobject()
2: CDO에 빈 포인터만 넣고 런타임에서 언리얼 오브젝트를 생성해 조합한다. (선택적 포함관계) // NewObject()

* TObjectPtr<T>: 헤더 내부 멤버 선언 시 사용

* FName 사용 시: TEXT("NAME_")

* UEnum Meta 정보
const UEnum* CardEnumType = FindObject<UEnum>(nullptr, TEXT("/Script/UnrealComposition.ECardType"));
if (CardEnumType)
{
FString CardMetaDatat = CardEnumType->GetDisplayNameTextByValue((int64)CardType).ToString();
UE_LOG(LogTemp, Log, TEXT("%s님이 소유한 카드 종류 %s"), *Person->GetName(), *CardMetaDatat);
}


📌 2-3. 언리얼 C++ 설계 3 - 델리게이트

1. 느슨한 결합(Loose Coupling)이 가지는 장점
- 향후 시스템 변경 사항에 대해 손쉽게 대처할 수 있다.
2. 느슨한 결합(Loose Coupling)으로 구현된 발행 구독 모델의 장점
- 클래스는 자신이 해야 할 작업에만 집중할 수 있다.
- 외부에서 발생한 변경 사항에 대해 영향받지 않는다.
- 자신의 기능을 확장하더라도 다른 모듈에 영향을 주지 않는다.
3. 언리얼 C++의 델리게이트의 선언 방법과 활용
- 몇 개의 인자를 가지는가?
- 어떤 방식으로 동작하는가? (MULTICAST 사용 유무 결정)
- 언리얼 에디터와 함께 연동할 것인가? (DYNAMIC 사용 유무 결정)
- 이를 조합해 적합한 매크로 선택

 

* 강한 결합과 느슨한 결합
- 실물에 의존하지 말고 추상적 설계에 의존하라.
Card가 아닌 Check 인터페이스 구현
- 델리게이트: 함수를 오브젝트처럼 관리
어떠한 객체의 멤버함수와 델리게이트를 연결해서 느슨한 결합 설계가 가능하다.

* 발행 구독 디자인 패턴
- 콘텐츠 제작자 / 발행자 <-> 구독자

* 델리게이트 선언
DECLARE_MULTICAST_DELEGATE_TwoParams(FCourseInfoOnChangedSignature)
FCourseInfoOnChangedSignature OnChanged;
void GetNotification(const FString& School, const FString& NewCourseInfo);
- 인자의 수와 타입 설계: 일대일/일대다, 저장/비저장
- DECLARE_{델리게이트유형}_DELEGATE_{함수정보}
- 델리게이트 유형
일대일: x
일대다: MULTICAST
일대일(BP지원): DYNAMIC
일대다(BP지원): DYNAMIC_MULTICAST
- 함수정보
인자x, 반환x: 공란
인자1, 반환x: OneParam
인자3, 반환o: RetVal_ThreeParams
인자는 최대 9까지
MULTICAST는 반환값을 지원하지 않는다.

* 델리게이트 바인딩
CourseInfo->OnChanged.AddUObject(Student1, &UStudent::GetNotification);

* 델리게이트 실행
void ChangedCourseInfo(const FString& InSchoolName, const FString& InNewContents);
UE_LOG(LogTemp, Log, TEXT("[CourseInfo] 학사 정보가 변경되어 알림을 발송합니다."));
OnChanged.Broadcast(InSchoolName, Contents);
CourseInfo->ChangedCourseInfo(SchoolName, TEXT("변경된 학사 정보"));