C++/C++ 문법과 핵심 개념

C++ 멤버 접근 제어 방법 - Getter/Setter, friend, using

loooong 2026. 8. 26. 22:13

클래스를 설계할 때 멤버 변수를 private으로 두는 경우가 많다.

외부에서 객체의 내부 상태를 직접 수정하지 못하게 하고, 클래스가 정해둔 방식으로만 상태를 변경하도록 만들기 위해서다.

 

그런데 실제 코드를 작성하다 보면 외부에서 private 멤버의 값을 읽거나 수정해야 하는 상황도 생긴다.

이때 단순히 멤버를 public으로 바꾸기보다는, 필요한 범위만 열어주는 여러 방법을 사용할 수 있다.

 

이번 글에서는 Getter/Setter, friend, 그리고 상속받은 멤버의 접근 수준을 다시 노출할 때 사용하는 using을 통해 C++의 접근 제어 방법을 정리해본다.

 


Getter와 Setter

가장 기본적인 방법은 public 멤버 함수를 통해 private 멤버에 접근하도록 만드는 것이다.

클래스 내부의 멤버 함수는 자신의 private 멤버에 접근할 수 있기 때문에 가능한 구조이다.

 

예를 들어 다음과 같이 HP를 private으로 두고 Getter와 Setter를 제공할 수 있다.

class Character
{
public:
    int GetPrivateHP()
    {
        return PrivateHP;
    }

    void SetPrivateHP(int InPrivateHP)
    {
        PrivateHP = InPrivateHP;
    }

private:
    int PrivateHP;
};

외부에서는 PrivateHP에 직접 접근하지 않고 공개된 함수를 사용한다.

Character Player;

Player.SetPrivateHP(100);

int HP = Player.GetPrivateHP();

 

겉으로 보면 단순히 값을 읽고 쓰는 것뿐이라 public 멤버와 큰 차이가 없어 보일 수 있다.

하지만 함수로 접근 경로를 만들면 나중에 값을 변경하는 규칙을 추가할 수 있다.

예를 들어 HP가 0보다 작아지지 않도록 제한할 수 있다.

class Character
{
public:
    int GetHP() const
    {
        return HP;
    }

    void SetHP(int InHP)
    {
        if (InHP < 0)
        {
            HP = 0;
            return;
        }

        HP = InHP;
    }

private:
    int HP = 100;
};

 

외부에서는 여전히 같은 방식으로 사용하지만,

Player.SetHP(-100);

실제 상태 변경 규칙은 Character가 관리한다.

 

즉, Getter와 Setter를 사용하는 이유는 단순히 private 멤버에 우회해서 접근하기 위해서라기보다, 객체가 자신의 상태 변경 규칙을 관리할 수 있도록 하기 위해서라고 볼 수 있다.

 


Setter가 항상 필요한 것은 아니다

멤버를 private으로 만들었다고 해서 무조건 Getter와 Setter를 둘 다 만들 필요는 없다.

예를 들어 HP는 외부에서 확인할 필요는 있지만 임의로 설정하게 하고 싶지는 않을 수 있다.

class Character
{
public:
    int GetHP() const
    {
        return HP;
    }

    void TakeDamage(int Damage)
    {
        HP -= Damage;
    }

private:
    int HP = 100;
};

이 경우 외부에서는 HP를 읽을 수 있지만 직접 값을 설정할 수는 없다.

Character Player;

int HP = Player.GetHP();

Player.TakeDamage(30);

 

오히려 다음처럼 모든 private 멤버에 기계적으로 Getter와 Setter를 만들어버리면,

Player.SetHP(999999);

 

결국 public 멤버로 공개한 것과 큰 차이가 없어질 수도 있다.

따라서 필요한 동작 자체를 함수로 제공하는 것이 더 자연스러운 경우도 많다.

TakeDamage()
Heal()
ConsumeMana()

 

즉,

private 데이터
        ↓
필요한 public 인터페이스만 제공

하는 형태가 캡슐화의 의도에 더 가깝다.

 


friend 키워드

때로는 특정 외부 함수나 특정 클래스만 내 클래스의 private, protected 멤버에 직접 접근하도록 허용하고 싶을 수 있다.

이때 friend 키워드를 사용해 권한을 부여할 수 있다.

 

friend 선언이 friend 클래스/함수에 대한 전방선언 역할도 같이 하기 때문에 일반적으로 별도의 전방선언이 필요 없다.

*다만 friend 함수는 이름 탐색 규칙이 일반적인 함수 선언과 조금 다를 수 있으므로, 외부에서 명시적으로 사용할 함수라면 별도의 선언을 두는 경우도 있다.

 


friend 함수

먼저 특정 함수 하나를 friend로 지정할 수 있다.

#include <iostream>
using namespace std;

class Character
{
private:
    int HP = 100;
    int MP = 50;

    friend void PrintCharacterInfo(const Character& CharacterObj);
};

void PrintCharacterInfo(const Character& CharacterObj)
{
    cout << "HP : " << CharacterObj.HP << '\n';
    cout << "MP : " << CharacterObj.MP << '\n';
}

 

PrintCharacterInfo()는 Character의 멤버 함수가 아니다.

하지만 Character 내부에서 friend로 선언했기 때문에 private 멤버에 접근할 수 있다.

 

중요한 점은 friend 함수가 클래스의 멤버가 되는 것은 아니라는 것이다.

사용할 때는 일반 함수와 동일하다.

int main()
{
    Character Player;

    PrintCharacterInfo(Player);

    return 0;
}

 


friend 클래스

함수 하나가 아니라 특정 클래스 전체에 접근 권한을 줄 수도 있다.

class Character
{
private:
    int HP = 100;
    int MP = 50;

    friend class GameManager;
};

class GameManager
{
public:
    void Heal(Character& CharacterObj)
    {
        CharacterObj.HP += 100; // private 접근 가능
        CharacterObj.MP += 50;  // private 접근 가능
    }
};

이제 GameManager의 멤버 함수에서는 Character의 private 멤버에 직접 접근할 수 있다.

 

전체 예시는 다음과 같다.

#include <iostream>
using namespace std;

class Character
{
private:
    int HP = 100;
    int MP = 50;

    friend void PrintCharacterInfo(const Character& CharacterObj);
    friend class GameManager;
};

class GameManager
{
public:
    void Heal(Character& CharacterObj)
    {
        CharacterObj.HP += 100;
        CharacterObj.MP += 50;
    }
};

void PrintCharacterInfo(const Character& CharacterObj)
{
    cout << "HP : " << CharacterObj.HP << '\n';
    cout << "MP : " << CharacterObj.MP << '\n';
}

int main()
{
    Character Player;

    GameManager Manager;

    PrintCharacterInfo(Player);

    Manager.Heal(Player);

    cout << "===== Heal =====\n";

    PrintCharacterInfo(Player);

    return 0;
}

 


friend는 단방향 관계이다

friend 관계는 자동으로 서로에게 적용되지 않는다.

예를 들어 A가 B를 friend로 선언했다고 하자.

class A
{
private:
    int ValueA;

    friend class B;
};

이 경우 B는 A의 private 멤버에 접근할 수 있다.

하지만 A가 자동으로 B의 private 멤버에 접근할 수 있는 것은 아니다.

 

즉,

A가 B를 friend로 선언

B → A private 접근 가능
A → B private 접근 가능하다는 뜻은 아님

이다.

필요하다면 B도 별도로 A를 friend로 선언해야 한다.

 


friend는 캡슐화를 완전히 무시하는 기능일까?

friend는 분명 외부 코드에 private 접근 권한을 제공하기 때문에 남용하면 캡슐화를 약하게 만들 수 있다.

그렇다고 반드시 피해야 하는 기능은 아니다.

핵심은 모든 코드에 데이터를 공개하는 것이 아니라 특정 함수나 특정 타입에만 제한적으로 권한을 제공한다는 점이다.

public
→ 모든 외부 코드에서 접근 가능

friend
→ 내가 지정한 특정 대상만 접근 가능

따라서 서로 강하게 협력하는 타입이나 연산자 오버로딩처럼 클래스 내부 데이터 접근이 자연스러운 경우 등에 사용할 수 있다.

다만 접근 권한이 필요한 대상이 많아지기 시작한다면 클래스의 책임이나 인터페이스 설계를 다시 확인할 필요가 있다.

 


using으로 상속받은 멤버를 다시 노출하기

using도 접근 제어와 관련해서 사용할 수 있다.

다만 using은 앞에서 본 Getter/Setter나 friend와는 역할이 조금 다르다.

 

using은 private 멤버에 접근할 수 있게 만들어주는 기능이 아니다.

상속을 통해 이미 접근 가능한 멤버의 이름을 현재 클래스의 다른 접근 영역에 다시 노출하는 데 사용할 수 있다.

 

예를 들어 부모 클래스에 protected 멤버가 있다고 하자.

class Character
{
protected:
    int HP;
    
    void Move()
    {
        std::cout << "Move\n";
    }
};

 

Monster는 Character를 public 상속하므로 HP에 접근할 수 있다.

하지만 외부에서는 여전히 HP가 protected이기 때문에 접근할 수 없다.

class Monster : public Character
{
};

int main()
{
    Monster M;

    M.HP = 100; // Error!
    M.Move();   // Error!
}

 

이때 using을 public 영역에 선언할 수 있다.

class Monster : public Character
{
public:
    using Character::HP;
    using Character::Move; // 메서드도 다시 노출 가능
};

그러면 Monster에서는 HP가 외부에 공개된다.

int main()
{
    Monster M;

    M.HP = 100; // 가능
    M.Move();   // 가능
}

 

또한, public 상속 받은 자식 클래스 객체에서도 public 접근성이 그대로 이어진다.

class BabyMonster : public Monster // public 상속
{
};

int main()
{
    BabyMonster M;
    M.Move(); // 가능
}

 

즉, 개념적으로 다음과 같은 변화이다.

Character::HP
protected

        ↓ public 영역에서 using

Monster::HP
public

 


using으로 private 멤버도 공개할 수 있을까?

불가능하다.

예를 들어 부모의 멤버가 private이라면,

class Character
{
private:
    int HP;
};

자식 클래스 자체가 HP에 접근할 수 없다.

따라서 다음과 같이 다시 노출할 수도 없다.

class Monster : public Character
{
public:
    using Character::HP; // Error!
};

 

using이 부모의 접근 제한을 무시하는 기능은 아니기 때문이다.

즉, using을 이렇게 이해하는 편이 좋다.

private을 뚫는 기능 X

이미 접근 가능한 상속 멤버를
현재 클래스의 접근 영역에 다시 노출하는 기능 O

 


Getter/Setter, friend, using은 서로 역할이 다르다

세 가지를 모두 접근 제어와 관련된 기능으로 볼 수 있지만 목적은 다르다.

방법 목적
Getter / Setter private 상태에 접근할 공개 인터페이스 제공
friend 특정 함수나 클래스에 private/protected 접근 권한 부여
using 상속받아 이미 접근 가능한 멤버를 다른 접근 수준으로 다시 노출

 

특히 using은 private 멤버에 접근하기 위한 방법으로 보면 안 된다.

Getter/Setter와 friend는 실제로 private 데이터와 관련된 접근 방법이지만, using은 상속 관계에서 접근 수준을 다시 노출하는 기능에 가깝다.

 


정리

클래스의 멤버를 private으로 만드는 이유는 단순히 외부에서 사용하지 못하게 막기 위해서가 아니다.

객체가 자신의 상태를 직접 관리하고, 외부에서는 필요한 인터페이스를 통해서만 상태를 다루도록 만들기 위함이다.

 

  • 가장 일반적인 방법은 public 멤버 함수를 통해 필요한 기능을 인터페이스로 제공하는 것이다.
    이를 통해 객체는 자신의 상태 변경 규칙을 유지할 수 있다.
  • 특정 함수나 클래스가 내부 데이터에 직접 접근해야 한다면,  단방향 접근 권한을 주는 friend를 사용할 수도 있다.
    다만 필요 이상으로 사용하면 클래스 간 결합도가 높아질 수 있다.
  • 마지막으로 using은 조금 성격이 다르다. 부모로부터 상속받아 이미 접근 가능한 멤버를 현재 클래스의 원하는 접근 영역에 다시 노출할 때 사용할 수 있다.