(코드 링크)
* 키워드
- 간단 설명: * 키워드는 곱셈, 포인터 변수 생성, 역참조라는 3가지 역할을 맡습니다.
- 곱셈: 매우 간단하게 2 * 3은 2 곱하기 3으로 해석됩니다.
- 포인터 변수 선언: C#에선 포인터라는 게 없는데, 그냥 클래스 변수가 포인터 변수, 즉 참조형 변수이고 구조체 변수가 일반 변수입니다. 포인터에 대해서는 이미 설명을 했기에 넘어가고, 간단하게 int* a;와 같이 형식 옆에 * 키워드를 붙여서 포인터 변수를 선언할 수 있습니다.
- 역참조: 역참조는 방금 설명한 참조형 변수, 즉 포인터가 참조하고 있는 객체를 역으로 가져올 때 쓰는 말입니다. int* pointer = new int(1); 이렇게 힙 메모리에 1이라는 값을 가진 int 타입 객체를 할당하고, 나중에 그 값이 필요해졌을 때 if (*pointer > 0)와 같이 역참조하여 값을 가져올 수 있습니다. 이렇게 역참조를 해야 하는 이유는 포인터 변수는 값이 변수에 있는 것이 아닌 힙 메모리에 존재하고, 변수에는 값이 아닌 주소가 저장되어 있기 때문에 역참조를 하지 않고 그대로 쓴다면 원래 '방개 아파트 12호에 사는 김방개가 똑똑하다'if (*pointer > 0)라고 말해야 할 것을 '방개 아파트 12호가 똑똑하다'if (pointer > 0)라고 말하는 것과 같습니다.
& 키워드
- 간단 설명: & 키워드는 비트 연산, 참조자 선언, 주소 연산의 3가지 역할을 맡습니다.
- 비트 연산: 비트 연산 중 AND 연산을 하는데, 둘 다 1이면 1, 그게 아니라면 0을 반환합니다.
- 참조자 선언: 참조자란 어떤 변수의 저주 인형 같은 변수입니다. 먼저 int a = 1; 이렇게 변수를 하나 생성하면 int& b = a;로 b라는 a의 저주 인형을 만들 수 있습니다. b의 값을 0으로 만들면 a의 값도 0이 되고 b에 100이라는 값을 할당해도 a에 100이라는 값이 할당됩니다. 물론 a에 10을 할당해도 b에 10이 할당됩니다. 그러면 저주 인형이 아니라 그냥 같은 존재가 아닌가 하는 생각이 들 수도 있는데, 사실 그것도 맞는 말입니다. 이게 무슨 소리냐 하면 a와 b가 완전히 같기 때문에 컴파일러의 입장에서는 둘 다 스택에 적재할 필요가 없고, 컴파일링을 할 때 b를 a로 바꿔주면 메모리를 아낄 수 있기 때문에 실제로 대부분의 경우에서 메모리에는 a만 남기 때문입니다.
- 매개 변수에서의 참조: 하지만 매개 변수에서 참조로 넘길 때는 상황이 좀 달라집니다. Foo(int& a)라는 함수가 있다고 해봅시다. 참조자는 초기화를 할 때 참조할 변수를 바로 대입해줘야 하기 때문에 저렇게 매개 변수 부분에 참조자를 사용해도 문제가 없습니다. 참조자는 선언할 때 바로 대입하지 않으면 컴파일 에러가 나기 때문입니다. 그런데 매개 변수에서 참조자를 사용할 때는 메모리에 참조자가 적재됩니다. c++ 에선 참조자를 사용한 call by reference가 포인터를 사용한 call by address와 컴파일러 입장에선 포인터로 전달받는 것과 대부분 같게 동작하기 때문에 메모리에 참조자가 할당됩니다. 메모리에 참조하는 객체의 주소가 적재된다는 것(성능에도 별로 영향 없음) 외에는 위에서 설명한 참조자와 같기 때문에 크게 신경 쓸 필요는 없을 것 같습니다.
- 주소 연산: 매개 변수에서의 참조에 대한 설명이 부족한데, 보통 매개 변수에 변수를 넣으면 그 변수에 있던 값이 복사됩니다. 그니까 int a = 10; Foo(a);라는 코드를 작성하면 Foo라는 함수에서 a라는 변수를 직접 바꾸는 것이 아닌 a에 있던 10이라는 값이 복사되어 그 복사된 값을 사용하게 됩니다. 그런데 a가 10이 아닌 엄청나게 메모리를 많이 차지하는 객체라서 복사할 수가 없다면? 또는 복사된 값이 아닌 a를 그대로 바꾸고 싶다면? 이런 상황에서 참조자 또는 주소 연산을 통해 a의 값이 아닌 주소를 복사해서 사용할 수 있습니다. 참조자는 방금 설명했으니 패스하고, Foo 메서드에 a의 주소를 넣고 Foo 메서드에서 받은 주소에 있는 객체를 바꾼다면 a도 바뀐다는 아이디어를 사용해 a를 직접 바꿀 수 있을 것 같기는 합니다. 그런데 문제는 a의 주소를 넣는 부분이죠. 힙에 할당된 객체는 포인터 변수로 주소를 저장하고, 그 주소를 바로 사용할 수 있지만 그 주소를 저장하는 일반적인 변수의 주소가 필요한 상황입니다. 이럴 때 주소 연산자인 & 키워드를 활용합니다. &a로 a의 주소에 접근할 수 있습니다. 그럼 메서드에도 Foo(&a);와 같이 사용하여 a를 참조할 수 있는 주소를 건네고 Foo 메서드에게 a를 마음대로 바꿀 수 있는 권한을 줄 수 있습니다.
- 코드 설명: 아래의 코드는 디자인 패턴 중 하나인 싱글톤 패턴을 구현한 씬 매니저의 .cpp 파일입니다. 2번째 줄에 있는 SceneManager* SceneManager::m_inst = nullptr;은 힙에 저장한 SceneManager 객체의 주소를 값으로 가지는 포인터에 대한 내용이고, nullptr로 초기화되는 모습입니다. 그다음으로 볼 것은 void SceneManager::ChangeScene(const std::string& _sceneName)라는 줄인데, const는 상수이기 때문에 함부로 바꾸지 말고 읽기만 하라는 뜻이고 string&이 변수의 저주 인형이라 볼 수 있는 참조자입니다. 그런데 저주 인형을 만들어 놓고 읽기만 하라는 건 무슨 의미일까요? 제가 아까 짧게 설명한 내용인데 메서드의 매개 변수에 값을 넣으면 값이 복사되고, 말 그대로 값이 하나가 더 생기는 것이기 때문에 크기가 큰 값은 복사하기가 꺼려집니다. 그리고 매개 변수에 참조자를 활용하면 포인터로 전달받는다고 했는데 여기서 포인터로 전달받는다는 것은 값이 아닌 값의 주소를 전달한다는 얘기이기 때문에 참조자를 사용하여 크기가 큰 '값'이 아닌 크기가 작은 '주소'를 복사하기 위해 참조자를 사용했습니다. 그리고 참조자는 저주 인형이기 때문에 매개 변수로 넣은 값을 바꾸면 원본 변수도 영향을 받습니다. 이 메서드에선 이 값을 읽고 map(C#에선 딕셔너리와 비슷)에서 키로 사용해 값을 찾는 역할이기 때문에 값을 바꾸면 안됩니다. 그런데 정신이 헤까닥 해서 전달받은 변수에 이상한 값을 할당할 수도 있고, 그렇기에 호출하는 사람은 이 메서드에 정신이 헤까닥 한 사람이 쓴 코드가 있는지 일일이 확인할 수도 있는데, 이런 사태를 예방하기 위해 const 키워드로 "이 변수 절대 안 바꿈"이라는 표식을 남겨 사고도 방지하고 이상한 값을 할당하지 않았다는 인증 마크를 단 셈입니다.
더보기
#include "SceneManager.h"
SceneManager* SceneManager::m_inst = nullptr;
void SceneManager::Update()
{
if (m_curScene)
m_curScene->Update();
}
void SceneManager::Render()
{
if (m_curScene)
m_curScene->Render();
}
void SceneManager::ChangeScene(const std::string& _sceneName)
{
auto iter = m_mapScenes.find(_sceneName);
if (iter == m_mapScenes.end())
return;
if (m_curScene)
m_curScene->Release();
//m_curScene = std::move(iter->second);
m_curScene = iter->second.get();
m_curScene->Init();
}
void SceneManager::RegisterScene(const std::string& _sceneName, std::unique_ptr<Scene> _scene)
{
m_mapScenes[_sceneName] = std::move(_scene);
}
- 관련 개념: 포인터, 클래스와 구조체(참조 관련)
- C#과의 차이점: C#에선 일단 포인터 변수가 따로 존재하는 게 아니기 때문에 * 키워드는 곱셈할 때 밖에는 못 봤습니다. C#에선 주소를 직접 다루지 않기도 하고, 변수를 참조로 전달하는 키워드가 있긴 하지만 & 키워드가 아닌 ref 키워드이기 때문에 이건 거의 써본 적이 없는 것 같습니다.
- 한줄평: C#이나 c++이나 참조에 관한 내용은 비슷하지만 c++은 좀 더 깊고 많이 다루는 개념이라 한 번 글로 정리해 보는 것도 나쁘지 않은 것 같습니다.
'C++ > CopyRogue' 카테고리의 다른 글
| [CopyRogue] 소스 파일(.cpp), 헤더 파일(.h) (0) | 2026.02.04 |
|---|