← SEARCHABLE ARCHIVE
C / C++ · 2026-06-03

C++ 기초부터 마스터까지 — 7화

const, 참조, 포인터의 차이

INTERACTIVE READING MODE →

본 블로그 작성 글들은, 강의를 목표로 하고 제작되었습니다. 또한 작성된 글의 정보도 틀렸을 수 있으니, 주의해주세요.

함수에 객체를 전달할 때는 값, 참조, 포인터 중 무엇을 사용할지 결정해야 한다. 겉으로 보기에는 비슷하지만 복사와 수정 가능성에서 차이가 크다.

값과 참조

void PrintByValue(Item item);           // 값 전달
void UpgradeItem(Item& item);           // 참조 전달
void PrintItem(const Item& item);       // 상수 참조 전달
// 값 전달과 const& 전달은 오버로드 해석에서 우열을 가릴 수 없어,
// 같은 이름으로 두면 PrintItem(x) 호출이 모호(ambiguous)해진다.

값으로 전달하면 함수가 별도의 복사본을 받을 수 있다. 작은 int 하나라면 별 문제가 없지만 문자열과 여러 멤버를 가진 객체라면 복사가 불필요할 수 있다.

Item&는 원본의 별칭이다. 함수 안에서 수정하면 원본도 수정된다. const Item&는 복사는 피하면서 원본 수정은 막는다. 읽기만 할 객체를 전달할 때 자주 사용하는 형태이다.

class Monster
{
public:
    int GetHealth() const
    {
        return health;
    }

    const std::string& GetName() const
    {
        return name;
    }

private:
    int health = 100;
    std::string name = "Slime";
};

함수 뒤의 const는 이 멤버 함수가 객체 상태를 변경하지 않는다는 뜻이다. const Monster에서도 호출할 수 있는 함수와 없는 함수가 여기서 갈린다.

문자열을 const std::string&로 반환하면 문자열 복사를 피할 수 있다. 다만 반환한 참조보다 원본 객체가 먼저 파괴되면 위험해진다는 점은 기억해야 한다.

포인터

포인터는 객체의 주소를 저장한다.

Monster monster;
Monster* target = &monster;

if (target != nullptr)
{
    std::cout << target->GetHealth() << '\n';
}

일반 객체는 .으로 접근하고 포인터가 가리키는 객체는 ->로 접근한다. 포인터는 아무것도 가리키지 않을 수도 있으므로 nullptr 검사가 필요하다.

여기서 중요한 점은 포인터가 객체를 가리킨다고 해서 반드시 그 객체를 소유하는 것은 아니라는 것이다. 누가 객체를 파괴할지 정해지지 않은 raw pointer는 코드가 커질수록 위험해진다.

Monster* target = nullptr;

{
    Monster monster;
    target = &monster;
}

// target은 이미 파괴된 monster의 주소를 들고 있다.

이런 포인터를 댕글링 포인터라고 부른다. 주소 자체는 남아 있어도 그 주소의 객체는 이미 수명이 끝났다.

참조는 반드시 유효한 객체를 가리켜야 하는 상황에 사용하기 좋고, 포인터는 대상이 없을 수 있거나 다른 대상을 가리킬 수 있는 상황에 어울린다.

그리고 포인터가 객체를 소유해야 한다면 raw pointer보다 스마트 포인터를 먼저 생각하는 것이 좋다.