C++ 클래스에서 데이터 멤버 (특히 UPROPERTY로 선언된 멤버)에 대한 캡슐화를 구현하는 대표적인 방식
Unreal의 객체 지향 설계 원칙에 따라, 외부에서 멤버 변수에 직접 접근하지 않고, Getter와 Setter 함수를 통해 간접적으로써 유효성 검사, 부가 처리, Blueprint와의 연동 등을 손쉽게 수행할 수 있다.
UFUNCTION(BlueprintCallable, Category = "Stats")
float GetHealth() const;
UFUNCTION(BlueprintCallable, Category = "Stats")
void SetHealth(float NewHealth);
UPROPERTY(EditAnywhere, BlueprintGetter=GetHealth, BlueprintSetter=SetHealth, Category = "Stats")
float Health;
UFUNCTION(BlueprintGetter)
float GetHealth() const { return Health; }
UFUNCTION(BlueprintSetter)
void SetHealth(float NewHealth) { Health = NewHealth; }
AMyActor* Actor = GetWorld()->SpawnActor<AMyActor>();
Actor->SetHealth(75.0f);
float CurrentHealth = Actor->GetHealth();
UE_LOG(LogTemp, Log, TEXT("Health is : %f"), CurrentHealth);
메타데이서 사용 방식과 기존 방식 차이점
1. BlueprintGetter / BlueprintSetter 메타데이터 사용 방식
UPROPERTY(EditAnywhere, BlueprintGetter=GetHealth, BlueprintSetter=SetHealth, Category = "Stats") float Health; UFUNCTION(BlueprintGetter) float GetHealth() const { return Health; } UFUNCTION(BlueprintSetter) void SetHealth(float NewHealth) { Health = NewHealth; }
- 장점
- 직접 UPROPERTY를 노출하면서도 접근을 Getter/Setter로 통제할 수 있다
- 변수는 직접 에디터에서 보이지만 Blueprint에서는 함수만을 통해 접근 가능하다
- UI 노출을 제어하면서도 캡슐화 유지가 가능하다
- 단점
- 코드 구조가 약간 복잡해진다
- 함수 이름과 변수 이름이 일치하지 않으면 혼란스러울 수 있다
- 모든 Getter/Setter가 Blueprint에서 꼭 필요하지 않을 때에도 함수가 생긴다
- 사용 적합한 상황들
- Blueprint 사용자가 해당 값을 읽고 쓸 수 있어야 하지만, 내부적으로는 조건 검사 또는 후처리가 필요할 경우
- 에디터에서 바로 변수 수정이 필요하고, 값 변경 시 부가 로직이 필요한 경우
2. 기존 방식
private: UPROPERTY(EditAnywhere) FString Name; public: UFUNCTION(BlueprintCallable) const FString& GetName() const; UFUNCTION(BlueprintCallable) void SetName(const FString& NewName);
- 장점
- 완전한 캡슐화(외부에서는 항상 Get/Set 함수를 통해 접근해야 하며, 멤버 변수는 보호된다.)
- 변수와 함수가 분리되므로 구조적으로 명확하다
- 리플렉션 시스템과는 연결되지만, 에디터에 직접 노출되지 않는다.(의도적으로 숨기고 싶을 때 유용)
- 단점
- 에디터에서 변수값을 직접 설정할 수 없다.(수동으로 초기화하거나 디폴트값 설정 필요)
- 간단한 데이터(예: 이름, 수치형 변수)조차 함수로만 접근해야 하므로 코드가 장황해질 수 있다
- 사용 적합한 상황들
- 변수 자체를 외부에 절대 노출시키지 않아야 하는 경우 (보안, 무결성 중요)
- 런타임에서만 값 접근이 필요한 경우 (에디터에서는 설정 불필요)
- 함수 내부에서 검사/로직이 복잡한 경우
const 위치
1. 함수 뒤에 붙는 const
float GetHealth() const;
- 해당 함수가 객체의 멤버 변수를 변경하지 않음을 보장한다.
- const 객치를 통해 호출할 수 있는 유일한 함수 유형
- 주로 Getter 함수에 붙음
예시
float AMyActor::GetHealth() const { return Health; // 멤버 변수 읽기만 할 경우 가능 } void AMyActor::SetHealth(float NewHealth) const { Health = NewHealth; // const 함수는 멤버 수정 불가능 }2. 매개 변수에 붙는 const
void SetName(const FString& NewName);
- 매개변수 NewName이 함수 내부에서 수정되지 않음을 나타낸다
- 참조(reference)나 포인터(pointer)를 통해 전달되는 복잡한 자료형(FString, FVector, FTransform등)의 복사 비용을 줄이고 의도를 명확하게 한다.
예시
void AMyActor::SetName(const FString& NewName) { Name = NewName; // 복사만 하고 수정은 불가능 }3. 반환 타입에 붙는 const
const FString& GetName() const;
- 반환되는 값을 함수 외부에서 수정하지 못하게 하기
- 참조 반환(reference return)일 때 유용하다.
예시
const FString& AMaActor::GetName() const { return Name; } // 외부 코드 FString Name = Actor->GetName(); // 가능 Actor->GetName() = TEXT("New"); // const 참조이므로 수정 불가능정리
- 함수 뒤에 const : 함수가 멤버를 변경하지 않음을 명시적으로 보장 --> 최적화, 안정성 향상
- 매개 변수에 const : 불필요한 복사 방지, 값 보호 --> 특히 FString, FVector, FTransform등에 유리
- 반환 타입에 const : 외부 코드가 반환값을 수정하지 못하게 막음 --> 참조 반환 시 사용