[2026] C++ friend 키워드 | Friend 가이드

[2026] C++ friend 키워드 | Friend 가이드

이 글의 핵심

C++ friend 키워드는 다른 클래스나 함수가 private 또는 protected 멤버에 접근할 수 있도록 허용합니다. 이는 캡슐화를 유지하면서도 특정 외부 함수나 클래스에게 제한적인 접근 권한을 부여하는 메커니즘입니다.

friend란?

friend 키워드는 다른 클래스나 함수가 private 또는 protected 멤버에 접근할 수 있도록 허용합니다. 이는 캡슐화를 유지하면서도 특정 외부 함수나 클래스에게 제한적인 접근 권한을 부여하는 메커니즘입니다. 다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// 타입 정의
class Box {
private:
    int width;
    
public:
    Box(int w) : width(w) {}
    
    // friend 함수 선언
    friend void printWidth(const Box& box);
};
// friend 함수 정의
void printWidth(const Box& box) {
    cout << "Width: " << box.width << endl;  // private 접근 가능
}
int main() {
    Box box(10);
    printWidth(box);  // Width: 10
}

왜 필요한가?:

  • 연산자 오버로딩: operator<<, operator+ 등을 비멤버 함수로 구현
  • 헬퍼 함수: 클래스와 밀접하게 관련된 유틸리티 함수
  • 클래스 간 협력: 두 클래스가 서로의 내부 구현을 알아야 할 때
  • 캡슐화 유지: public getter/setter 없이 선택적 접근 다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// ❌ public getter: 모든 코드가 접근 가능
// 타입 정의
class Box {
public:
    int getWidth() const { return width; }
private:
    int width;
};
// ✅ friend: 특정 함수만 접근 가능
class Box {
private:
    int width;
    friend void printWidth(const Box& box);
};

friend의 종류:

  1. friend 함수: 특정 함수가 private 멤버에 접근
  2. friend 클래스: 특정 클래스의 모든 멤버 함수가 접근
  3. friend 멤버 함수: 특정 클래스의 특정 멤버 함수만 접근 아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
class A {
private:
    int data;
    
    // friend 함수
    friend void func(const A& a);
    
    // friend 클래스
    friend class B;
    
    // friend 멤버 함수
    friend void C::process(const A& a);
};

friend 함수

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class Point {
private:
    int x, y;
    
public:
    Point(int x, int y) : x(x), y(y) {}
    
    // friend 함수
    friend double distance(const Point& p1, const Point& p2);
    friend void print(const Point& p);
};
double distance(const Point& p1, const Point& p2) {
    int dx = p1.x - p2.x;
    int dy = p1.y - p2.y;
    return sqrt(dx * dx + dy * dy);
}
void print(const Point& p) {
    cout << "(" << p.x << ", " << p.y << ")" << endl;
}
int main() {
    Point p1(0, 0);
    Point p2(3, 4);
    
    cout << "Distance: " << distance(p1, p2) << endl;  // 5
    print(p1);  // (0, 0)
}

friend 클래스

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class Engine {
private:
    int horsepower;
    
public:
    Engine(int hp) : horsepower(hp) {}
    
    // Car 클래스를 friend로 선언
    friend class Car;
};
class Car {
private:
    Engine engine;
    string model;
    
public:
    Car(const string& m, int hp) : model(m), engine(hp) {}
    
    void printInfo() {
        cout << "Model: " << model << endl;
        cout << "Horsepower: " << engine.horsepower << endl;  // private 접근
    }
};
int main() {
    Car car("Tesla", 450);
    car.printInfo();
}

연산자 오버로딩

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class Complex {
private:
    double real, imag;
    
public:
    Complex(double r = 0, double i = 0) : real(r), imag(i) {}
    
    // friend 연산자
    friend Complex operator+(const Complex& c1, const Complex& c2);
    friend ostream& operator<<(ostream& os, const Complex& c);
};
Complex operator+(const Complex& c1, const Complex& c2) {
    return Complex(c1.real + c2.real, c1.imag + c2.imag);
}
ostream& operator<<(ostream& os, const Complex& c) {
    os << c.real;
    if (c.imag >= 0) {
        os << "+" << c.imag << "i";
    } else {
        os << c.imag << "i";
    }
    return os;
}
int main() {
    Complex c1(3, 4);
    Complex c2(1, 2);
    Complex c3 = c1 + c2;
    
    cout << c3 << endl;  // 4+6i
}

실전 예시

예시 1: 행렬

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다, 반복문으로 데이터를 처리합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class Matrix {
private:
    vector<vector<int>> data;
    int rows, cols;
    
public:
    Matrix(int r, int c) : rows(r), cols(c), data(r, vector<int>(c, 0)) {}
    
    friend Matrix operator*(const Matrix& m1, const Matrix& m2);
    friend ostream& operator<<(ostream& os, const Matrix& m);
};
Matrix operator*(const Matrix& m1, const Matrix& m2) {
    if (m1.cols != m2.rows) {
        throw invalid_argument("행렬 곱셈 불가");
    }
    
    Matrix result(m1.rows, m2.cols);
    
    for (int i = 0; i < m1.rows; i++) {
        for (int j = 0; j < m2.cols; j++) {
            for (int k = 0; k < m1.cols; k++) {
                result.data[i][j] += m1.data[i][k] * m2.data[k][j];
            }
        }
    }
    
    return result;
}
ostream& operator<<(ostream& os, const Matrix& m) {
    for (int i = 0; i < m.rows; i++) {
        for (int j = 0; j < m.cols; j++) {
            os << m.data[i][j] << " ";
        }
        os << endl;
    }
    return os;
}

예시 2: 분수

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 에러 처리를 통해 안정성을 확보합니다, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class Fraction {
private:
    int numerator;
    int denominator;
    
    int gcd(int a, int b) {
        return b == 0 ? a : gcd(b, a % b);
    }
    
    void simplify() {
        int g = gcd(abs(numerator), abs(denominator));
        numerator /= g;
        denominator /= g;
    }
    
public:
    Fraction(int n = 0, int d = 1) : numerator(n), denominator(d) {
        if (d == 0) {
            throw invalid_argument("분모는 0이 될 수 없음");
        }
        simplify();
    }
    
    friend Fraction operator+(const Fraction& f1, const Fraction& f2);
    friend Fraction operator*(const Fraction& f1, const Fraction& f2);
    friend bool operator==(const Fraction& f1, const Fraction& f2);
    friend ostream& operator<<(ostream& os, const Fraction& f);
};
Fraction operator+(const Fraction& f1, const Fraction& f2) {
    int n = f1.numerator * f2.denominator + f2.numerator * f1.denominator;
    int d = f1.denominator * f2.denominator;
    return Fraction(n, d);
}
Fraction operator*(const Fraction& f1, const Fraction& f2) {
    return Fraction(f1.numerator * f2.numerator, 
                   f1.denominator * f2.denominator);
}
bool operator==(const Fraction& f1, const Fraction& f2) {
    return f1.numerator == f2.numerator && 
           f1.denominator == f2.denominator;
}
ostream& operator<<(ostream& os, const Fraction& f) {
    os << f.numerator << "/" << f.denominator;
    return os;
}
int main() {
    Fraction f1(1, 2);
    Fraction f2(1, 3);
    
    cout << f1 + f2 << endl;  // 5/6
    cout << f1 * f2 << endl;  // 1/6
}

예시 3: 스트림 연산자

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class Person {
private:
    string name;
    int age;
    
public:
    Person(const string& n, int a) : name(n), age(a) {}
    
    friend ostream& operator<<(ostream& os, const Person& p);
    friend istream& operator>>(istream& is, Person& p);
};
ostream& operator<<(ostream& os, const Person& p) {
    os << "Name: " << p.name << ", Age: " << p.age;
    return os;
}
istream& operator>>(istream& is, Person& p) {
    cout << "이름: ";
    is >> p.name;
    cout << "나이: ";
    is >> p.age;
    return is;
}
int main() {
    Person p1("Alice", 30);
    cout << p1 << endl;
    
    Person p2("", 0);
    cin >> p2;
    cout << p2 << endl;
}

friend vs getter/setter

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// friend 사용
class Point {
private:
    int x, y;
    
public:
    Point(int x, int y) : x(x), y(y) {}
    
    friend double distance(const Point& p1, const Point& p2);
};
double distance(const Point& p1, const Point& p2) {
    int dx = p1.x - p2.x;  // 직접 접근
    int dy = p1.y - p2.y;
    return sqrt(dx * dx + dy * dy);
}
// getter 사용
class Point {
private:
    int x, y;
    
public:
    Point(int x, int y) : x(x), y(y) {}
    
    int getX() const { return x; }
    int getY() const { return y; }
};
double distance(const Point& p1, const Point& p2) {
    int dx = p1.getX() - p2.getX();  // getter 사용
    int dy = p1.getY() - p2.getY();
    return sqrt(dx * dx + dy * dy);
}

자주 발생하는 문제

문제 1: friend 남용

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ 캡슐화 파괴
class BadClass {
private:
    int data;
    
public:
    friend class A;
    friend class B;
    friend class C;
    // 너무 많은 friend
};
// ✅ 최소한의 friend
class GoodClass {
private:
    int data;
    
public:
    int getData() const { return data; }
    void setData(int d) { data = d; }
};

문제 2: 양방향 friend

아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ 순환 의존
class A {
    friend class B;
};
class B {
    friend class A;
};
// ✅ 필요한 경우만 friend

문제 3: friend 상속 안됨

아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class Base {
private:
    int data;
    
public:
    friend void func(const Base& b);
};
class Derived : public Base {
    // func은 Derived의 friend 아님
};

실무 패턴

패턴 1: 팩토리 함수

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class DatabaseConnection {
private:
    std::string connectionString_;
    bool connected_ = false;
    
    // private 생성자
    DatabaseConnection(const std::string& connStr) 
        : connectionString_(connStr) {}
    
public:
    // friend 팩토리 함수
    friend DatabaseConnection createConnection(const std::string& host, int port);
    
    void connect() {
        connected_ = true;
        std::cout << "연결됨: " << connectionString_ << '\n';
    }
};
// 팩토리 함수가 private 생성자 호출
DatabaseConnection createConnection(const std::string& host, int port) {
    std::string connStr = host + ":" + std::to_string(port);
    return DatabaseConnection(connStr);
}
// 사용
auto conn = createConnection("localhost", 5432);
conn.connect();

패턴 2: 비교 연산자

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class Date {
private:
    int year_, month_, day_;
    
public:
    Date(int y, int m, int d) : year_(y), month_(m), day_(d) {}
    
    // friend 비교 연산자
    friend bool operator==(const Date& lhs, const Date& rhs);
    friend bool operator<(const Date& lhs, const Date& rhs);
};
bool operator==(const Date& lhs, const Date& rhs) {
    return lhs.year_ == rhs.year_ &&
           lhs.month_ == rhs.month_ &&
           lhs.day_ == rhs.day_;
}
bool operator<(const Date& lhs, const Date& rhs) {
    if (lhs.year_ != rhs.year_) return lhs.year_ < rhs.year_;
    if (lhs.month_ != rhs.month_) return lhs.month_ < rhs.month_;
    return lhs.day_ < rhs.day_;
}
// 사용
Date d1(2026, 3, 12);
Date d2(2026, 3, 13);
if (d1 < d2) {
    std::cout << "d1이 더 이른 날짜\n";
}

패턴 3: 테스트 헬퍼

다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며, 조건문으로 분기 처리를 수행합니다. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class BankAccount {
private:
    double balance_;
    std::string accountNumber_;
    
public:
    BankAccount(const std::string& accNum, double initialBalance)
        : accountNumber_(accNum), balance_(initialBalance) {}
    
    void deposit(double amount) {
        balance_ += amount;
    }
    
    // 테스트 헬퍼를 friend로 선언
    friend class BankAccountTest;
};
// 테스트 클래스
class BankAccountTest {
public:
    static void verifyBalance(const BankAccount& account, double expected) {
        if (account.balance_ == expected) {
            std::cout << "테스트 통과\n";
        } else {
            std::cout << "테스트 실패: " << account.balance_ << " != " << expected << '\n';
        }
    }
};
// 사용
BankAccount acc("123456", 1000.0);
acc.deposit(500.0);
BankAccountTest::verifyBalance(acc, 1500.0);

friend 함수 vs friend 클래스: 역할 정리

구분friend 함수friend 클래스
범위선언된 그 자유 함수 하나(또는 오버로드 집합의 일부)만 접근해당 클래스의 모든 멤버 함수가 grantor의 비공개 멤버에 접근
ODR·네임스페이스클래스 안에 선언해도 클래스 멤버가 아님 — 네임스페이스 스코프에서 정의friend 클래스 자체는 여전히 별도 타입; “전체 허용”이라 변경 영향이 큼
유지보수필요한 함수만 좁게 열 수 있어 변경 범위가 작음한 번 열면 그 클래스 전체가 내부에 의존 — 결합도 상승
friend 멤버 함수(friend void Other::f(const X&);)는 “특정 타입의 특정 메서드만” 열 때 쓰며, Other 선언이 앞에 있어야 하는 등 선언 순서를 맞춰야 합니다.

연산자 오버로딩에서의 활용 (심화)

  • 대칭 이항 연산: operator+(T,T)를 비멤버로 두면 좌측·우측 변환 규칙이 자연스럽습니다. private 멤버를 읽으려면 friend 또는 public 접근자가 필요합니다.
  • 스트림 연산자 operator<<, operator>>: 첫 번째 인자가 std::ostream&이므로 멤버로 넣기 어렵고, 비멤버 + friend 선언이 관례입니다.
  • 멤버 vs 비멤버: operator+=는 보통 멤버, operator+는 비멤버 friend 조합이 흔합니다. 일관된 네이밍·예외 보장도 friend 본문 안에서 처리합니다. 아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 코드를 직접 실행해보면서 동작을 확인해보세요.
class Vec {
    double x, y;
    friend Vec operator+(Vec a, Vec b) {
        return {a.x + b.x, a.y + b.y};
    }
};

캡슐화와의 관계

friend는 “private를 없애는 것”이 아니라, 검증된 동료에게만 문을 여는 것에 가깝습니다.

  • 장점: 불필요한 public getter 남발을 줄이고, 불변 조건(invariant)을 깨지 않는 경로만 열 수 있습니다.
  • 단점: friend 본문은 클래스 내부 표현에 결합됩니다. grantee 코드가 바뀌면 friend 구현도 같이 수정되는 경우가 많습니다. Effective C++ 계열에서 말하듯, 비멤버 비friend 함수가 가능하면 그쪽이 의존성이 가장 적습니다. friend는 “정말 private가 필요할 때만” 선택합니다.

실전 라이브러리·프레임워크 패턴

  • 단위 테스트: 테스트 픽스처나 friend class FooTest로 불변식만 검증하고, 프로덕션에서는 숨깁니다(남용 시 테스트 전용 로직이 프로덕션 헤더에 노출되므로 팀 규칙이 필요합니다).
  • 모듈 내부 협력자: 같은 컴포넌트 안의 detail 네임스페이스 함수만 friend로 두고, 외부 API는 좁게 유지합니다.
  • 직렬화: boost::serialization 스타일에서 serialize를 friend로 두는 패턴이 과거에 흔했습니다. 최근에는 리플렉션·코드젠 대안도 검토합니다.

남용 시 신호와 대안

  • 신호: friend 선언이 수십 줄로 늘어남, 서로 다른 팀 모듈이 서로 friend, “편해서” public API를 안 만들고 전부 friend.
  • 대안: PIMPL로 구현 세부 숨기기, 무명 네임스페이스 + 팩토리, 인터페이스 클래스로 접근 지점 최소화, C++20 모듈로 구현 단위 캡슐화.

FAQ

Q1: friend는 언제 사용해야 하나요?

A:

  • 연산자 오버로딩: operator<<, operator+ 등을 비멤버 함수로 구현
  • 헬퍼 함수: 클래스와 밀접하게 관련된 유틸리티 함수
  • 밀접한 클래스 간 협력: 두 클래스가 서로의 내부 구현을 알아야 할 때
  • 팩토리 함수: private 생성자를 호출하는 팩토리 패턴 아래 코드는 cpp를 사용한 구현 예제입니다. 코드를 직접 실행해보면서 동작을 확인해보세요.
// 연산자 오버로딩
friend std::ostream& operator<<(std::ostream& os, const MyClass& obj);
// 헬퍼 함수
friend double distance(const Point& p1, const Point& p2);

Q2: friend는 캡슐화를 파괴하나요?

A: 과도한 사용은 문제입니다. friend는 캡슐화를 제한적으로 완화하는 메커니즘이므로, 필요한 경우에만 최소한으로 사용해야 합니다. 다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

// ❌ 남용: 너무 많은 friend
class BadClass {
private:
    int data;
    friend class A;
    friend class B;
    friend class C;
    friend class D;
};
// ✅ 적절한 사용: 필요한 경우만
class GoodClass {
private:
    int data;
    friend std::ostream& operator<<(std::ostream& os, const GoodClass& obj);
};

Q3: friend 함수는 멤버 함수인가요?

A: 아니요. friend 함수는 클래스의 멤버 함수가 아니라 독립적인 함수입니다. 클래스 내부에 선언되지만, 클래스 스코프에 속하지 않습니다. 아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 코드를 직접 실행해보면서 동작을 확인해보세요.

class MyClass {
    friend void func(const MyClass& obj);  // 멤버 함수 아님
};
// 독립적인 함수
void func(const MyClass& obj) {
    // obj의 private 멤버 접근 가능
}

Q4: friend는 상속되나요?

A: 아니요. friend 관계는 상속되지 않습니다. 파생 클래스는 기반 클래스의 friend를 자동으로 상속하지 않습니다. 다음은 cpp를 활용한 상세한 구현 코드입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.

class Base {
private:
    int data;
    friend void func(const Base& b);
};
class Derived : public Base {
    // func은 Derived의 friend가 아님
};
void func(const Base& b) {
    // b.data 접근 가능
}
void func(const Derived& d) {
    // d.data 접근 불가 (Base의 private)
}

Q5: friend vs public getter/setter?

A:

  • friend: 선택적 접근 (특정 함수/클래스만)
  • public getter/setter: 모든 코드가 접근 가능 아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 각 부분의 역할을 이해하면서 코드를 살펴보시기 바랍니다.
// friend: 특정 함수만 접근
class Box {
private:
    int width;
    friend void printWidth(const Box& box);
};
// public: 모든 코드가 접근
class Box {
public:
    int getWidth() const { return width; }
private:
    int width;
};

선택 기준:

  • 특정 함수/클래스만 접근해야 하면: friend
  • 모든 코드가 접근해야 하면: public getter/setter

Q6: friend 함수와 멤버 함수 중 어느 것을 사용해야 하나요?

A:

  • 멤버 함수: 객체의 상태를 변경하거나 객체에 밀접하게 관련된 경우
  • friend 함수: 두 객체를 대칭적으로 다루거나 연산자 오버로딩 아래 코드는 cpp를 사용한 구현 예제입니다. 클래스를 정의하여 데이터와 기능을 캡슐화하며. 코드를 직접 실행해보면서 동작을 확인해보세요.
// 멤버 함수: 객체 상태 변경
class Point {
    void move(int dx, int dy) { x += dx; y += dy; }
};
// friend 함수: 대칭적 연산
class Point {
    friend double distance(const Point& p1, const Point& p2);
};

Q7: friend 학습 리소스는?

A:

  • “Effective C++” (3rd Edition) by Scott Meyers (Item 23: Prefer non-member non-friend functions)
  • cppreference.com - Friend declaration
  • “C++ Primer” (5th Edition) by Stanley Lippman 관련 글: Operator Overloading, Access Control. 한 줄 요약: friend는 특정 함수나 클래스가 private 멤버에 접근할 수 있도록 허용하는 메커니즘입니다.

같이 보면 좋은 글 (내부 링크)

이 주제와 연결되는 다른 글입니다.

관련 글

... 996 lines not shown ... Token usage: 63706/1000000; 936294 remaining Start-Sleep -Seconds 3