-> vs . vs ::언리얼 C++ 코드를 읽다 보면 ->, ., ::가 굉장히 자주 등장합니다.
처음에는 셋 다 대충 "~의" 정도로 읽혀서 헷갈리기 쉬운데, 실제로는 접근하는 대상이 다릅니다.
| 연산자 | 간단하게 읽으면 | 언제 사용하는가 |
|---|---|---|
-> | 이 포인터가 가리키는 객체의 | 포인터를 통해 멤버에 접근 |
. | 이 객체의 | 객체 자체에서 멤버에 접근 |
:: | 이 클래스/범위에 속한 | 특정 클래스나 범위를 지정 |
-> : 포인터가 가리키는 객체의->는 포인터를 통해 그 객체의 변수나 함수에 접근할 때 사용합니다.
PlayerController->MoveAction
말로 풀어 읽으면 다음과 같습니다.
PlayerController포인터가 가리키는 객체의MoveAction
즉, PlayerController가 객체 그 자체가 아니라 객체를 가리키는 포인터이기 때문에 .이 아니라 ->를 사용합니다.
AJupiterPlayerController* PlayerController;
여기서 *가 붙어 있으므로 PlayerController는 AJupiterPlayerController 객체를 가리킬 수 있는 포인터 변수입니다.
따라서 다음과 같이 사용합니다.
PlayerController->MoveAction
. : 객체 자체의.은 포인터가 아니라 객체 자체를 가지고 있을 때 그 객체의 멤버에 접근하는 연산자입니다.
예를 들어 다음과 같은 객체가 있다고 가정해보겠습니다.
AJupiterPlayerController PlayerController;
이 경우 PlayerController는 포인터가 아니라 객체 자체이므로 다음과 같이 접근합니다.
PlayerController.MoveAction
즉,
PlayerController.MoveAction
은
PlayerController객체의MoveAction
이라고 읽을 수 있습니다.
반면,
PlayerController->MoveAction
은
PlayerController포인터가 가리키는 객체의MoveAction
이라고 읽을 수 있습니다.
객체.멤버
포인터->멤버
이렇게 기억하면 비교적 간단합니다.

:: : 특정 클래스나 범위에 속한 것::는 범위 지정 연산자(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라는 이름으로 받아옵니다.
따라서 전체적으로는 대략 다음과 같이 읽을 수 있습니다.
AJupiterCharacter의SetupPlayerInputComponent함수를 정의한다.
이 함수는UInputComponent를 가리키는 포인터를PlayerInputComponent라는 이름으로 전달받는다.
Super::SetupPlayerInputComponent(PlayerInputComponent);
여기서 Super는 언리얼에서 부모 클래스를 가리키는 별칭입니다.
따라서 다음과 같이 이해할 수 있습니다.
부모 클래스의
SetupPlayerInputComponent()를 먼저 실행한다.
AJupiterCharacter가 ACharacter를 상속받았다면, 여기서 Super는 부모인 ACharacter를 의미하게 됩니다.
if (UEnhancedInputComponent* EnhancedInput =
Cast<UEnhancedInputComponent>(PlayerInputComponent))
캐스팅(Casting)은 여기서는 쉽게 말해,
"이 객체를
UEnhancedInputComponent로 다룰 수 있는지 확인하고, 가능하다면 그 타입의 포인터를 얻는다."
라고 이해할 수 있습니다.
Cast<UEnhancedInputComponent>(PlayerInputComponent)가 성공하면 결과로 유효한 포인터가 반환되고, 그것을
UEnhancedInputComponent* EnhancedInput
에 저장합니다.
실패하면 nullptr가 반환되므로 if의 내부가 실행되지 않습니다.
즉,
PlayerInputComponent가UEnhancedInputComponent로 캐스팅 가능한지 확인한다. 성공하면 그 결과를EnhancedInput이라는 지역 포인터 변수에 담고 아래 코드를 실행한다.
라고 읽을 수 있습니다.
if (AJupiterPlayerController* PlayerController =
Cast<AJupiterPlayerController>(GetController()))
이것도 같은 구조입니다.
GetController()로 현재 Pawn/Character를 제어하고 있는 Controller를 가져온 뒤, 그것이 AJupiterPlayerController로 다룰 수 있는지 확인합니다.
성공하면 그 포인터를
AJupiterPlayerController* PlayerController
에 저장합니다.
따라서 다음처럼 읽을 수 있습니다.
GetController()가 반환한 Controller를AJupiterPlayerController로 캐스팅한다. 성공하면 그 포인터를PlayerController라는 지역 변수에 저장하고if문 내부를 실행한다.
실패하면 nullptr이 반환되므로 내부 코드가 실행되지 않습니다.
PlayerController->MoveActionif (PlayerController->MoveAction)
앞에서 PlayerController를 다음과 같이 선언했습니다.
AJupiterPlayerController* PlayerController
즉, 포인터입니다.
따라서 .이 아니라 ->를 사용합니다.
PlayerController->MoveAction
은
PlayerController포인터가 가리키는 객체의MoveAction
이라고 읽습니다.
그리고 if에 넣었으므로 여기서는 MoveAction이 유효한지 확인하는 역할을 합니다.
MoveAction이 실제로 설정되어 있다면 아래 코드를 실행한다.
BindAction() 읽어보기마지막으로 가장 복잡해 보이는 부분입니다.
EnhancedInput->BindAction(
PlayerController->MoveAction,
ETriggerEvent::Triggered,
this,
&AJupiterCharacter::Move
);
한꺼번에 보면 복잡하지만 하나씩 분리하면 생각보다 단순합니다.
EnhancedInput->BindActionEnhancedInput->BindAction(...)
EnhancedInput은 포인터이므로 ->를 사용합니다.
EnhancedInput포인터가 가리키는 객체의BindAction()함수를 실행한다.
PlayerController->MoveActionPlayerController->MoveAction
PlayerController포인터가 가리키는 객체의MoveAction
즉, 어떤 Input Action을 연결할 것인지 전달합니다.
ETriggerEvent::TriggeredETriggerEvent::Triggered
여기서는 ::가 사용되었습니다.
ETriggerEvent라는 범위에 정의되어 있는Triggered
즉, 해당 Input Action이 Triggered 상태일 때 함수를 실행하도록 지정합니다.
thisthis
this는 현재 객체 자신을 가리키는 포인터입니다.
현재 코드가 AJupiterCharacter 객체에서 실행되고 있으므로,
"바로 이
AJupiterCharacter객체에서"
정도로 우선 이해할 수 있습니다.
&AJupiterCharacter::Move&AJupiterCharacter::Move
여기서 ::가 다시 등장합니다.
AJupiterCharacter::Move
는
AJupiterCharacter클래스에 속한Move함수
라는 의미입니다.
앞의 &까지 포함하면 해당 멤버 함수를 BindAction()에 연결할 함수로 전달하는 형태가 됩니다.
즉, 여기서는
실제로 실행할 함수는
AJupiterCharacter의Move함수다.
정도로 이해하면 됩니다.
if (PlayerController->MoveAction)
{
EnhancedInput->BindAction(
PlayerController->MoveAction,
ETriggerEvent::Triggered,
this,
&AJupiterCharacter::Move
);
}
이를 자연어로 바꾸면 다음과 같습니다.
PlayerController가 가지고 있는MoveAction이 유효하다면, 그 액션을 Enhanced Input에 연결한다.
MoveAction이Triggered될 때 현재AJupiterCharacter객체의Move함수가 실행되도록 바인딩한다.
결국 이 코드의 핵심은 입력 액션과 실제 C++ 함수를 연결하는 것입니다.
코드를 읽을 때 세 연산자를 전부 똑같은 "~의"라고 생각하면 헷갈리기 쉽습니다.
다음처럼 구분해서 읽는 편이 좋습니다.
PlayerController.MoveAction
// PlayerController 객체의 MoveAction
PlayerController->MoveAction
// PlayerController 포인터가 가리키는 객체의 MoveAction
AJupiterCharacter::Move
// AJupiterCharacter 클래스에 속한 Move
따라서 기억할 핵심은 다음 세 가지입니다.
| 코드 | 읽는 방법 |
|---|---|
A.B | A 객체의 B |
A->B | A 포인터가 가리키는 객체의 B |
A::B | A라는 범위에 속한 B |
특히 언리얼 C++에서는 객체를 포인터 형태로 다루는 경우가 많기 때문에 ->를 매우 자주 만나게 됩니다.
처음에는 기호 자체를 외우기보다는 코드를 볼 때마다 "지금 왼쪽에 있는 것이 객체인가, 포인터인가, 아니면 클래스/범위 이름인가?"를 확인하는 방식으로 읽는 것이 이해하기 쉽습니다.