icon안동민 개발노트

접근 지정자 (Access Specifiers)


접근 지정자의 개념과 중요성

 접근 지정자는 객체 지향 프로그래밍의 핵심 원칙 중 하나인 캡슐화를 구현하는 데 사용되는 키워드입니다. C++에서는 세 가지 접근 지정자를 제공합니다.

  1. public
  2. private
  3. protected

 이들은 클래스의 멤버(변수와 함수)에 대한 접근 권한을 제어하여 데이터 은닉과 추상화를 가능하게 합니다.

public 접근 지정자

 public 멤버는 클래스 외부에서 자유롭게 접근할 수 있습니다. 주로 클래스의 인터페이스를 정의하는 데 사용됩니다.

class Person {
public:
    std::string name;
    void introduce() {
        std::cout << "My name is " << name << std::endl;
    }
};
 
int main() {
    Person p;
    p.name = "Alice";  // 직접 접근 가능
    p.introduce();     // 직접 호출 가능
    return 0;
}

 사용 시 주의사항

  • public 멤버는 클래스의 사용자가 직접 조작할 수 있으므로, 클래스의 불변성(invariant)을 해칠 수 있는 위험이 있습니다.
  • 인터페이스의 안정성을 위해 public 멤버는 신중히 선택해야 합니다.

private 접근 지정자

 private 멤버는 해당 클래스 내부에서만 접근할 수 있습니다. 이는 데이터 은닉의 핵심 메커니즘입니다.

class BankAccount {
private:
    double balance;
 
public:
    void deposit(double amount) {
        if (amount > 0) {
            balance += amount;
        }
    }
 
    double getBalance() const {
        return balance;
    }
};
 
int main() {
    BankAccount account;
    // account.balance = 1000;  // 컴파일 에러: private 멤버에 접근 불가
    account.deposit(1000);     // OK: public 메서드를 통한 간접 접근
    std::cout << account.getBalance() << std::endl;  // OK: public 메서드를 통한 조회
    return 0;
}

 장점

  1. 데이터 보호 : 무단 접근과 수정을 방지합니다.
  2. 유지보수성 향상 : 내부 구현을 변경해도 외부 코드에 영향을 미치지 않습니다.
  3. 사용자 인터페이스 단순화 : 필요한 기능만 노출합니다.

protected 접근 지정자

 protected 멤버는 해당 클래스와 그 파생 클래스에서 접근할 수 있습니다. 이는 상속 관계에서 유용합니다.

class Animal {
protected:
    std::string name;
 
public:
    Animal(const std::string& n) : name(n) {}
    virtual void makeSound() = 0;
};
 
class Dog : public Animal {
public:
    Dog(const std::string& n) : Animal(n) {}
    void makeSound() override {
        std::cout << name << " says Woof!" << std::endl;  // protected 멤버 접근 가능
    }
};

 사용 시 주의사항

  • protected 멤버는 상속 계층 구조를 복잡하게 만들 수 있으므로 신중히 사용해야 합니다.
  • 과도한 사용은 캡슐화를 약화시킬 수 있습니다.

접근 지정자의 상속

 클래스를 상속할 때, 기본 클래스의 접근 지정자는 다음과 같이 상속됩니다.

  1. public 상속
    기본 클래스의 public 멤버 → 파생 클래스의 public 멤버
    기본 클래스의 protected 멤버 → 파생 클래스의 protected 멤버
  2. protected 상속 : 기본 클래스의 public과 protected 멤버 → 파생 클래스의 protected 멤버
  3. private 상속 : 기본 클래스의 public과 protected 멤버 → 파생 클래스의 private 멤버
class Base {
public:
    int x;
protected:
    int y;
private:
    int z;
};
 
class DerivedPublic : public Base {
    // x는 public
    // y는 protected
    // z는 접근 불가
};
 
class DerivedProtected : protected Base {
    // x와 y는 protected
    // z는 접근 불가
};
 
class DerivedPrivate : private Base {
    // x와 y는 private
    // z는 접근 불가
};

friend 키워드

 friend 키워드를 사용하면 외부 함수나 클래스에 privateprotected 멤버에 대한 접근 권한을 부여할 수 있습니다.

class Box {
private:
    double width, height, depth;
 
public:
    Box(double w, double h, double d) : width(w), height(h), depth(d) {}
 
    friend double calculateVolume(const Box& b);
    friend class BoxManager;
};
 
double calculateVolume(const Box& b) {
    return b.width * b.height * b.depth;  // private 멤버에 접근 가능
}
 
class BoxManager {
public:
    void printBoxDimensions(const Box& b) {
        std::cout << "Width: " << b.width << ", Height: " << b.height << ", Depth: " << b.depth << std::endl;
    }
};

 사용 시 주의사항

  • friend의 과도한 사용은 캡슐화를 약화시킬 수 있으므로 신중히 사용해야 합니다.
  • 양방향 관계나 유틸리티 함수 구현 등 제한적인 상황에서만 사용하는 것이 좋습니다.

실습 : 학교 관리 시스템

 다음 요구사항을 만족하는 간단한 학교 관리 시스템을 구현해보세요.

  1. Person 클래스를 기본 클래스로 사용
  2. StudentTeacher 클래스를 Person에서 파생
  3. 적절한 접근 지정자를 사용하여 데이터 은닉과 상속 구현
  4. friend 함수를 사용하여 학생과 교사 정보를 출력하는 기능 구현
#include <iostream>
#include <string>
#include <vector>
 
class Person {
protected:
    std::string name;
    int age;
 
public:
    Person(const std::string& n, int a) : name(n), age(a) {}
    virtual void introduce() const = 0;
};
 
class Student : public Person {
private:
    int studentId;
    std::vector<double> grades;
 
public:
    Student(const std::string& n, int a, int id) : Person(n, a), studentId(id) {}
 
    void addGrade(double grade) {
        grades.push_back(grade);
    }
 
    void introduce() const override {
        std::cout << "I'm a student named " << name << ", age " << age << ", ID " << studentId << std::endl;
    }
 
    friend void printStudentInfo(const Student& s);
};
 
class Teacher : public Person {
private:
    std::string subject;
 
public:
    Teacher(const std::string& n, int a, const std::string& subj) : Person(n, a), subject(subj) {}
 
    void introduce() const override {
        std::cout << "I'm a teacher named " << name << ", age " << age << ", teaching " << subject << std::endl;
    }
 
    friend void printTeacherInfo(const Teacher& t);
};
 
void printStudentInfo(const Student& s) {
    std::cout << "Student Information:" << std::endl;
    std::cout << "Name: " << s.name << ", Age: " << s.age << ", ID: " << s.studentId << std::endl;
    std::cout << "Grades: ";
    for (double grade : s.grades) {
        std::cout << grade << " ";
    }
    std::cout << std::endl;
}
 
void printTeacherInfo(const Teacher& t) {
    std::cout << "Teacher Information:" << std::endl;
    std::cout << "Name: " << t.name << ", Age: " << t.age << ", Subject: " << t.subject << std::endl;
}
 
int main() {
    Student alice("Alice", 20, 12345);
    alice.addGrade(85.0);
    alice.addGrade(92.0);
 
    Teacher bob("Bob", 35, "Mathematics");
 
    alice.introduce();
    bob.introduce();
 
    printStudentInfo(alice);
    printTeacherInfo(bob);
 
    return 0;
}

연습 문제

  1. BankAccount 클래스를 구현하세요. 이 클래스는 계좌 번호, 소유자 이름, 잔액을 private 멤버로 가지고, 입금, 출금, 잔액 조회 기능을 public 메서드로 제공해야 합니다. 또한, TransactionLog 클래스를 friend로 지정하여 모든 거래 내역을 기록할 수 있도록 구현하세요.
  2. Shape를 기본 클래스로 하고, Circle, Rectangle, Triangle을 파생 클래스로 구현하세요. 각 클래스는 적절한 접근 지정자를 사용하여 데이터 은닉과 상속을 구현해야 합니다. 또한, 각 도형의 넓이를 계산하는 가상 함수를 포함해야 합니다.
  3. Car 클래스를 구현하고, Engine 클래스를 Car의 friend로 지정하세요. Car 클래스는 private 멤버로 연료량을 가지고 있어야 하며, Engine 클래스는 Car의 연료를 소모하는 메서드를 구현해야 합니다.


참고 자료

  • Stroustrup, Bjarne. "The C++ Programming Language (4th Edition)"
  • Meyers, Scott. "Effective C++ : 55 Specific Ways to Improve Your Programs and Designs"
  • Sutter, Herb and Alexandrescu, Andrei. "C++ Coding Standards: 101 Rules, Guidelines, and Best Practices"
  • C++ Reference : Access specifiers
  • ISO C++ Core Guidelines: C.9 : Minimize exposure of members