오늘은 어제 해결한 GunManager nullptr 문제에 이어서 성능과 안전성 측면에서 추가 리팩토링을 진행했음
기존 방식은 TArray를 값으로 반환하여 매번 복사 비용이 발생하고, 포인터 매개변수로 인한 null 안전성 문제가 있었음
새로운 방식은 const 참조 반환과 캐시 시스템으로 성능을 최적화하고, 참조 매개변수 사용으로 컴파일 타임 null 안전성을 확보하는 구조로 개선했음
코드 리뷰 중에 GunManager의 성능과 안전성 문제점들을 발견했음
기존 코드의 문제점:
// ❌ 문제 1: 매번 배열 복사 발생
TArray<EGunType> GetOwnedGunList() const
{
TArray<EGunType> KeyArray;
OwnedGuns.GenerateKeyArray(KeyArray); // 매번 새로운 배열 생성
return KeyArray; // 복사 비용 발생
}
// ❌ 문제 2: null 안전성 부족
void UpdateGunData(AGun* CurrGun) // nullptr 전달 가능
void SetCurrentGun(AGun* NextGun) // null 체크 없음
성능 영향 분석:
GetOwnedGunList() 호출 시마다 메모리 할당과 복사 발생핵심 아이디어: "참조 반환 + 캐시 시스템 + 참조 매개변수"
개선 방향 설정:
성능 최적화: TArray 복사 → const 참조 반환 + 캐시
안전성 강화: 포인터 매개변수 → 참조 매개변수
예상 성능 개선:
반환 타입 변경
// 기존: 값 반환 (복사 비용 발생)
TArray<EGunType> GetOwnedGunList() const
// 개선: const 참조 반환 (복사 비용 제거)
const TArray<EGunType>& GetOwnedGunList()
캐시 시스템 도입
class UGunManager : public UActorComponent
{
protected:
TMap<EGunType, int32> OwnedGuns;
// 캐시된 총기 리스트 (성능 최적화)
TArray<EGunType> CachedGunList;
bool bGunListCacheDirty = true;
public:
const TArray<EGunType>& GetOwnedGunList();
};
캐시 업데이트 전략
const TArray<EGunType>& UGunManager::GetOwnedGunList()
{
// 캐시가 더티한 경우에만 업데이트
if (bGunListCacheDirty)
{
CachedGunList.Empty();
OwnedGuns.GenerateKeyArray(CachedGunList);
bGunListCacheDirty = false;
}
return CachedGunList; // 참조 반환 (복사 없음)
}
캐시 무효화 시점
void UGunManager::UpdateGunData(const AGun& CurrGun)
{
OwnedGuns.Add(CurrGun.GetGunType(), CurrGun.GetCurrentAmmo());
bGunListCacheDirty = true; // 데이터 변경 시 캐시 무효화
}
매개변수 타입 개선
// 기존: 포인터 사용 (null 위험)
void UpdateGunData(AGun* CurrGun) // nullptr 전달 가능
void SetCurrentGun(AGun* NextGun) // 매번 null 체크 필요
// 개선: 참조 사용 (컴파일 타임 안전성)
void UpdateGunData(const AGun& CurrGun) // null 불가능
void SetCurrentGun(AGun& NextGun) // null 체크 불필요
참조 매개변수의 장점
PlayerCharacter에서 사용 방식 변경
// 기존: 포인터 직접 전달 (위험)
GunManager->UpdateGunData(CurrWeapon); // ❌ null 위험
// 개선: null 체크 후 역참조 (안전)
if (CurrWeapon)
{
GunManager->UpdateGunData(*CurrWeapon); // ✅ 안전한 참조 전달
}
const 참조로 받아서 복사 비용 제거
// 기존: 값으로 받기 (복사 발생)
TArray<EGunType> GunList = GunManager->GetOwnedGunList(); // ❌ 복사
// 개선: const 참조로 받기 (복사 없음)
const TArray<EGunType>& GunList = GunManager->GetOwnedGunList(); // ✅ 참조
개선 전후 비교
| 항목 | 기존 방식 | 개선된 방식 | 개선 효과 |
|---|---|---|---|
| 메모리 할당 | 매번 발생 (O(n)) | 캐시 활용 (O(1)) | 100% 제거 |
| 복사 비용 | TArray 전체 복사 | 참조 반환 | 100% 제거 |
| null 안전성 | 런타임 체크 필요 | 컴파일 타임 보장 | 완전 보장 |
| 코드 복잡성 | 포인터 + null 체크 | 참조 매개변수 | 대폭 감소 |
실제 사용 시나리오 개선:
달성한 개선사항:
1. 불필요한 복사 피하기
- 값 반환 → const 참조 반환
- 매개변수 복사 → const 참조 매개변수
2. 캐시 친화적 설계
- 자주 사용되는 데이터는 캐시해서 재사용
- 데이터 변경 시에만 캐시 무효화
3. 컴파일 타임 안전성 확보
- 포인터 → 참조로 null 안전성 보장
- 타입 시스템 활용한 에러 방지
// TArray 사용 시 주의사항
TArray<T> GetData() const; // ❌ 복사 발생
const TArray<T>& GetData() const; // ✅ 참조 반환
// 매개변수 전달 방식
void ProcessData(TArray<T> Data); // ❌ 전체 복사
void ProcessData(const TArray<T>& Data); // ✅ 참조 전달