VOD 2강 복습

주소리·2026년 8월 21일

TIL

목록 보기
45/48

C++에서 헷갈리는 연산자 정리: -> vs . vs ::

언리얼 C++ 코드를 읽다 보면 ->, ., ::가 굉장히 자주 등장합니다.

처음에는 셋 다 대충 "~의" 정도로 읽혀서 헷갈리기 쉬운데, 실제로는 접근하는 대상이 다릅니다.

연산자간단하게 읽으면언제 사용하는가
->이 포인터가 가리키는 객체의포인터를 통해 멤버에 접근
.이 객체의객체 자체에서 멤버에 접근
::이 클래스/범위에 속한특정 클래스나 범위를 지정

1. -> : 포인터가 가리키는 객체의

->포인터를 통해 그 객체의 변수나 함수에 접근할 때 사용합니다.

PlayerController->MoveAction

말로 풀어 읽으면 다음과 같습니다.

PlayerController 포인터가 가리키는 객체의 MoveAction

즉, PlayerController가 객체 그 자체가 아니라 객체를 가리키는 포인터이기 때문에 .이 아니라 ->를 사용합니다.

AJupiterPlayerController* PlayerController;

여기서 *가 붙어 있으므로 PlayerControllerAJupiterPlayerController 객체를 가리킬 수 있는 포인터 변수입니다.

따라서 다음과 같이 사용합니다.

PlayerController->MoveAction

2. . : 객체 자체의

.은 포인터가 아니라 객체 자체를 가지고 있을 때 그 객체의 멤버에 접근하는 연산자입니다.

예를 들어 다음과 같은 객체가 있다고 가정해보겠습니다.

AJupiterPlayerController PlayerController;

이 경우 PlayerController는 포인터가 아니라 객체 자체이므로 다음과 같이 접근합니다.

PlayerController.MoveAction

즉,

PlayerController.MoveAction

PlayerController 객체의 MoveAction

이라고 읽을 수 있습니다.

반면,

PlayerController->MoveAction

PlayerController 포인터가 가리키는 객체의 MoveAction

이라고 읽을 수 있습니다.

한 줄 정리

객체.멤버
포인터->멤버

이렇게 기억하면 비교적 간단합니다.


3. :: : 특정 클래스나 범위에 속한 것

::범위 지정 연산자(Scope Resolution Operator)입니다.

쉽게 말하면,

":: 앞에 있는 범위에 속한 것을 사용하겠다."

라고 읽을 수 있습니다.

예를 들어 다음 코드를 보겠습니다.

AJupiterCharacter::Move

이는

AJupiterCharacter 클래스에 속한 Move

라는 뜻입니다.

함수를 클래스 밖에서 정의할 때도 사용합니다.

void AJupiterCharacter::Move()
{
}

이는

지금 정의하고 있는 Move()AJupiterCharacter 클래스의 Move()다.

라는 의미입니다.

따라서 세 연산자를 아주 단순화하면 다음처럼 구분할 수 있습니다.

Object.Member             // 객체의 Member
Pointer->Member           // 포인터가 가리키는 객체의 Member
ClassName::Member         // 해당 클래스/범위에 속한 Member

실제 코드 읽어보기

이번에는 Enhanced Input을 연결하는 코드를 한 줄씩 읽어보겠습니다.

void AJupiterCharacter::SetupPlayerInputComponent(
    UInputComponent* PlayerInputComponent)

먼저 AJupiterCharacter::SetupPlayerInputComponent

AJupiterCharacter 클래스에 속한 SetupPlayerInputComponent 함수를 정의한다.

라고 읽을 수 있습니다.

그리고

UInputComponent* PlayerInputComponent

는 함수의 매개변수입니다.

UInputComponent 객체를 가리킬 수 있는 포인터를 PlayerInputComponent라는 이름으로 받아옵니다.

따라서 전체적으로는 대략 다음과 같이 읽을 수 있습니다.

AJupiterCharacterSetupPlayerInputComponent 함수를 정의한다.
이 함수는 UInputComponent를 가리키는 포인터를 PlayerInputComponent라는 이름으로 전달받는다.


부모 클래스의 함수 호출

Super::SetupPlayerInputComponent(PlayerInputComponent);

여기서 Super는 언리얼에서 부모 클래스를 가리키는 별칭입니다.

따라서 다음과 같이 이해할 수 있습니다.

부모 클래스의 SetupPlayerInputComponent()를 먼저 실행한다.

AJupiterCharacterACharacter를 상속받았다면, 여기서 Super는 부모인 ACharacter를 의미하게 됩니다.


Enhanced Input으로 캐스팅

if (UEnhancedInputComponent* EnhancedInput =
    Cast<UEnhancedInputComponent>(PlayerInputComponent))

캐스팅(Casting)은 여기서는 쉽게 말해,

"이 객체를 UEnhancedInputComponent로 다룰 수 있는지 확인하고, 가능하다면 그 타입의 포인터를 얻는다."

라고 이해할 수 있습니다.

Cast<UEnhancedInputComponent>(PlayerInputComponent)가 성공하면 결과로 유효한 포인터가 반환되고, 그것을

UEnhancedInputComponent* EnhancedInput

에 저장합니다.

실패하면 nullptr가 반환되므로 if의 내부가 실행되지 않습니다.

즉,

PlayerInputComponentUEnhancedInputComponent로 캐스팅 가능한지 확인한다. 성공하면 그 결과를 EnhancedInput이라는 지역 포인터 변수에 담고 아래 코드를 실행한다.

라고 읽을 수 있습니다.


PlayerController 캐스팅

if (AJupiterPlayerController* PlayerController =
    Cast<AJupiterPlayerController>(GetController()))

이것도 같은 구조입니다.

GetController()로 현재 Pawn/Character를 제어하고 있는 Controller를 가져온 뒤, 그것이 AJupiterPlayerController로 다룰 수 있는지 확인합니다.

성공하면 그 포인터를

AJupiterPlayerController* PlayerController

에 저장합니다.

따라서 다음처럼 읽을 수 있습니다.

GetController()가 반환한 Controller를 AJupiterPlayerController로 캐스팅한다. 성공하면 그 포인터를 PlayerController라는 지역 변수에 저장하고 if문 내부를 실행한다.

실패하면 nullptr이 반환되므로 내부 코드가 실행되지 않습니다.


PlayerController->MoveAction

if (PlayerController->MoveAction)

앞에서 PlayerController를 다음과 같이 선언했습니다.

AJupiterPlayerController* PlayerController

즉, 포인터입니다.

따라서 .이 아니라 ->를 사용합니다.

PlayerController->MoveAction

PlayerController 포인터가 가리키는 객체의 MoveAction

이라고 읽습니다.

그리고 if에 넣었으므로 여기서는 MoveAction이 유효한지 확인하는 역할을 합니다.

MoveAction이 실제로 설정되어 있다면 아래 코드를 실행한다.


BindAction() 읽어보기

마지막으로 가장 복잡해 보이는 부분입니다.

EnhancedInput->BindAction(
    PlayerController->MoveAction,
    ETriggerEvent::Triggered,
    this,
    &AJupiterCharacter::Move
);

한꺼번에 보면 복잡하지만 하나씩 분리하면 생각보다 단순합니다.

EnhancedInput->BindAction

EnhancedInput->BindAction(...)

EnhancedInput은 포인터이므로 ->를 사용합니다.

EnhancedInput 포인터가 가리키는 객체의 BindAction() 함수를 실행한다.


PlayerController->MoveAction

PlayerController->MoveAction

PlayerController 포인터가 가리키는 객체의 MoveAction

즉, 어떤 Input Action을 연결할 것인지 전달합니다.


ETriggerEvent::Triggered

ETriggerEvent::Triggered

여기서는 ::가 사용되었습니다.

ETriggerEvent라는 범위에 정의되어 있는 Triggered

즉, 해당 Input Action이 Triggered 상태일 때 함수를 실행하도록 지정합니다.


this

this

this현재 객체 자신을 가리키는 포인터입니다.

현재 코드가 AJupiterCharacter 객체에서 실행되고 있으므로,

"바로 이 AJupiterCharacter 객체에서"

정도로 우선 이해할 수 있습니다.


&AJupiterCharacter::Move

&AJupiterCharacter::Move

여기서 ::가 다시 등장합니다.

AJupiterCharacter::Move

AJupiterCharacter 클래스에 속한 Move 함수

라는 의미입니다.

앞의 &까지 포함하면 해당 멤버 함수를 BindAction()연결할 함수로 전달하는 형태가 됩니다.

즉, 여기서는

실제로 실행할 함수는 AJupiterCharacterMove 함수다.

정도로 이해하면 됩니다.


전체 코드를 문장으로 읽어보기

if (PlayerController->MoveAction)
{
    EnhancedInput->BindAction(
        PlayerController->MoveAction,
        ETriggerEvent::Triggered,
        this,
        &AJupiterCharacter::Move
    );
}

이를 자연어로 바꾸면 다음과 같습니다.

PlayerController가 가지고 있는 MoveAction이 유효하다면, 그 액션을 Enhanced Input에 연결한다.
MoveActionTriggered될 때 현재 AJupiterCharacter 객체의 Move 함수가 실행되도록 바인딩한다.

결국 이 코드의 핵심은 입력 액션과 실제 C++ 함수를 연결하는 것입니다.


최종 정리

코드를 읽을 때 세 연산자를 전부 똑같은 "~의"라고 생각하면 헷갈리기 쉽습니다.

다음처럼 구분해서 읽는 편이 좋습니다.

PlayerController.MoveAction
// PlayerController 객체의 MoveAction

PlayerController->MoveAction
// PlayerController 포인터가 가리키는 객체의 MoveAction

AJupiterCharacter::Move
// AJupiterCharacter 클래스에 속한 Move

따라서 기억할 핵심은 다음 세 가지입니다.

코드읽는 방법
A.BA 객체의 B
A->BA 포인터가 가리키는 객체의 B
A::BA라는 범위에 속한 B

특히 언리얼 C++에서는 객체를 포인터 형태로 다루는 경우가 많기 때문에 ->를 매우 자주 만나게 됩니다.

처음에는 기호 자체를 외우기보다는 코드를 볼 때마다 "지금 왼쪽에 있는 것이 객체인가, 포인터인가, 아니면 클래스/범위 이름인가?"를 확인하는 방식으로 읽는 것이 이해하기 쉽습니다.

profile
게임개발 및 아트, TA

0개의 댓글