icon
9장 : 객체 지향 프로그래밍 기초

접근 지정자

지난 장에서 publicprivate이라는 키워드를 클래스 정의 내에서 사용했는데, 이것이 바로 접근 지정자(Access Specifier) 입니다.

이번 장에서는 C++ 클래스에서 멤버(변수와 함수)에 대한 접근 권한을 제어하는 세 가지 접근 지정자, 즉 public, private, 그리고 protected에 대해 심도 있게 다루겠습니다.

이들은 캡슐화(Encapsulation) 라는 객체 지향 프로그래밍의 핵심 원칙을 구현하는 데 필수적인 요소입니다.


접근 지정자의 역할과 캡슐화

접근 지정자는 클래스의 멤버(멤버 변수와 멤버 함수)가 클래스 외부에서 얼마나 접근 가능한지를 명시하는 키워드입니다.

이를 통해 클래스 사용자가 클래스의 내부 구현에 직접 접근하여 객체의 상태를 훼손하는 것을 막고, 오직 클래스가 의도한 방식으로만 객체와 상호작용하도록 강제합니다.

이러한 접근 제어는 캡슐화(Encapsulation) 를 구현하는 핵심 요소입니다.

캡슐화는 데이터와 데이터를 처리하는 함수를 하나의 단위(클래스)로 묶고, 외부로부터의 직접적인 접근을 제한하여 정보 은닉(Information Hiding) 을 달성하는 객체 지향의 주요 원칙입니다.

  • 정보 은닉의 장점
    • 데이터 보호: 객체의 내부 상태가 외부로부터 오염되거나 오용되는 것을 방지합니다.
    • 유지보수성 향상: 클래스의 내부 구현이 변경되어도 외부에 노출된 인터페이스(public 멤버)만 변경되지 않는다면, 클래스를 사용하는 외부 코드에 영향을 미치지 않습니다.
    • 코드 재사용성 증가: 잘 캡슐화된 클래스는 독립적으로 동작하며 다른 프로그램에서도 쉽게 재사용될 수 있습니다.
    • 디버깅 용이성: 문제가 발생했을 때, 문제가 발생한 범위를 좁히는 데 도움이 됩니다.

C++에는 세 가지 접근 지정자가 있습니다

  1. public (공개)
  2. private (비공개)
  3. protected (보호된)

public 접근 지정자

public으로 선언된 멤버는 클래스 외부 어디에서든 접근할 수 있습니다.

이는 클래스가 외부와 소통하는 인터페이스(Interface) 역할을 합니다.

객체의 동작을 제어하기 위해 외부에서 호출할 수 있는 멤버 함수나, 객체의 상태를 나타내는 데 필요한 멤버 변수 등을 public으로 선언합니다.

특징

  • 클래스 객체를 통해 직접 접근 가능.
  • 클래스 내부, 상속받은 클래스 내부에서도 접근 가능.
public 접근 지정자 예시
#include <iostream>
#include <string>

class User {
public: // public 접근 지정자
    std::string username; // 외부에서 직접 접근 가능
    std::string email;    // 외부에서 직접 접근 가능

    void displayUserInfo() { // 외부에서 직접 호출 가능
        std::cout << "사용자 이름: " << username << ", 이메일: " << email << std::endl;
    }
};

int main() {
    User user1;

    // public 멤버 변수에 직접 접근하여 값 할당
    user1.username = "Alice";
    user1.email = "alice@example.com";

    // public 멤버 함수 호출
    user1.displayUserInfo(); // 출력: 사용자 이름: Alice, 이메일: alice@example.com

    // 외부에서 public 멤버에 직접 접근 가능
    std::cout << "직접 접근: " << user1.username << std::endl;

    return 0;
}

private 접근 지정자

private으로 선언된 멤버는 오직 해당 클래스의 멤버 함수 내에서만 접근할 수 있습니다.

클래스 외부에서는 직접 접근할 수 없습니다.

이는 객체의 내부 상태를 보호하고 캡슐화를 강화하는 데 사용됩니다.

특징

  • 클래스 외부에서 직접 접근 불가능.
  • 클래스 내부의 멤버 함수에서는 접근 가능.
  • 상속받은 클래스에서도 직접 접근 불가능.
private 접근 지정자 예시
#include <iostream>
#include <string>

class BankAccount {
private: // private 접근 지정자
    std::string accountNumber; // 계좌번호 (외부에서 직접 접근 불가)
    double balance;            // 잔액 (외부에서 직접 접근 불가)

public:
    // 생성자 (다음 장에서 자세히 다룸)
    BankAccount(std::string accNum, double initialBalance) {
        accountNumber = accNum;
        if (initialBalance >= 0) {
            balance = initialBalance;
        } else {
            balance = 0;
            std::cerr << "초기 잔액은 음수가 될 수 없습니다. 0으로 설정합니다." << std::endl;
        }
    }

    // public 멤버 함수 (private 멤버에 접근하여 작업 수행)
    void deposit(double amount) { // 입금
        if (amount > 0) {
            balance += amount;
            std::cout << amount << "원 입금되었습니다. 현재 잔액: " << balance << std::endl;
        } else {
            std::cout << "유효하지 않은 입금액입니다." << std::endl;
        }
    }

    void withdraw(double amount) { // 출금
        if (amount > 0 && amount <= balance) {
            balance -= amount;
            std::cout << amount << "원 출금되었습니다. 현재 잔액: " << balance << std::endl;
        } else {
            std::cout << "출금 실패: 잔액이 부족하거나 유효하지 않은 금액입니다." << std::endl;
        }
    }

    // 잔액을 확인하는 게터 함수 (private 멤버의 값을 외부에 안전하게 노출)
    double getBalance() {
        return balance;
    }

    std::string getAccountNumber() { // 계좌번호 게터
        return accountNumber;
    }
};

int main() {
    BankAccount myAccount("123-456-7890", 1000.0);

    // myAccount.balance = -500; // 컴파일 오류! balance는 private이므로 직접 접근 불가

    std::cout << "초기 잔액: " << myAccount.getBalance() << std::endl; // 게터를 통해 잔액 확인

    myAccount.deposit(500.0);   // public 함수를 통해 입금
    myAccount.withdraw(200.0);  // public 함수를 통해 출금
    myAccount.withdraw(2000.0); // 잔액 부족으로 출금 실패

    std::cout << "최종 잔액: " << myAccount.getBalance() << std::endl;

    return 0;
}

위 예시에서 accountNumberbalanceprivate으로 선언되어 외부에서 직접 접근하거나 변경할 수 없습니다. 대신 deposit(), withdraw(), getBalance()와 같은 public 멤버 함수를 통해 balance에 간접적으로 접근하고 조작할 수 있습니다. 이는 유효성 검사(예: 음수 잔액 방지, 유효하지 않은 입출금액)와 같은 로직을 적용하여 데이터의 무결성을 보장하는 데 매우 효과적입니다.


protected 접근 지정자

protected로 선언된 멤버는 privatepublic의 중간 정도의 접근 권한을 가집니다.

특징

  • 해당 클래스의 멤버 함수 내에서 접근 가능. (private과 동일)
  • 해당 클래스를 상속받은 자식(파생) 클래스의 멤버 함수 내에서 접근 가능. (private과 다름)
  • 클래스 외부에서는 직접 접근 불가능. (private과 동일)

protected는 주로 상속 관계에서 부모 클래스의 일부 멤버를 자식 클래스가 접근하고 싶지만, 외부에는 노출하고 싶지 않을 때 사용됩니다. 상속에 대한 자세한 내용은 이후에 다룰 것이므로, 지금은 개념만 이해해 두시면 됩니다.

protected 접근 지정자 예시
class Animal {
protected: // protected 멤버
    std::string species; // 종은 자식 클래스가 접근 가능
    int age;             // 나이도 자식 클래스가 접근 가능

public:
    Animal(std::string s, int a) : species(s), age(a) {}
    void displaySpecies() {
        std::cout << "종: " << species << std::endl;
    }
};

class Dog : public Animal { // Dog 클래스가 Animal 클래스를 상속받음
public:
    Dog(std::string name, int age) : Animal("개", age) { // 부모 클래스의 생성자 호출
        // 자식 클래스인 Dog의 멤버 함수에서 protected 멤버인 age에 접근 가능
        std::cout << "새로운 강아지 객체 생성! 나이: " << this->age << std::endl;
    }

    void bark() {
        std::cout << "멍멍!" << std::endl;
    }
};

int main() {
    Dog myDog("바둑이", 3);
    myDog.displaySpecies(); // Animal의 public 함수 호출
    myDog.bark();

    // myDog.age = 4; // 컴파일 오류: age는 protected이므로 외부에서 직접 접근 불가
    return 0;
}

기본 접근 지정자

클래스에서 명시적으로 접근 지정자를 선언하지 않으면, 기본적으로 모든 멤버는 private 으로 간주됩니다. (struct 키워드로 클래스를 선언하면 기본적으로 public입니다.)

기본 접근 지정자 예시
class MyClass { // class 키워드 사용
    int privateVar; // 명시하지 않으면 private
public:
    int publicVar;
};

struct MyStruct { // struct 키워드 사용
    int publicVar; // 명시하지 않으면 public
private:
    int privateVar;
};

일반적으로 C++에서는 객체 지향적인 설계를 위해 class 키워드를 사용하여 클래스를 정의하고, 멤버 변수는 private 또는 protected로 선언하여 캡슐화를 강화합니다.