본문으로 건너뛰기
Previous
Next
C++ friend Keyword: Access Control, Operators,

C++ friend Keyword: Access Control, Operators,

C++ friend Keyword: Access Control, Operators,

이 글의 핵심

friend functions and classes grant access to private and protected members. When to use friend for operators and factories, when to avoid it, and how it compares to getters.

What is friend?

By default, private members of a class are accessible only by the class’s own member functions. The friend keyword grants another function or class permission to access those private members:

class Box {
    int width_;  // private

public:
    Box(int w) : width_(w) {}

    // Declare printWidth as a friend — it can access width_
    friend void printWidth(const Box& box);
};

// Not a member, but has friend access to Box's private members
void printWidth(const Box& box) {
    std::cout << "Width: " << box.width_ << '\n';  // OK — friend access
}

int main() {
    Box b(42);
    printWidth(b);  // prints: Width: 42
}

The friend declaration lives inside the class definition, but the friend function itself is not a member — it’s a regular free function with special access.


Friend Functions vs Member Functions

The choice between a friend function and a member function matters for operators:

class Vector2D {
    double x_, y_;

public:
    Vector2D(double x, double y) : x_(x), y_(y) {}

    // Member function: first operand must be a Vector2D
    Vector2D operator+(const Vector2D& other) const {
        return {x_ + other.x_, y_ + other.y_};
    }

    // Member function: can access private fields directly
    double length() const {
        return std::sqrt(x_ * x_ + y_ * y_);
    }

    // Friend non-member: both operands treated symmetrically
    friend Vector2D operator*(double scalar, const Vector2D& v) {
        return {scalar * v.x_, scalar * v.y_};
    }

    // operator<< must be non-member (ostream is on the left)
    friend std::ostream& operator<<(std::ostream& os, const Vector2D& v) {
        return os << '(' << v.x_ << ", " << v.y_ << ')';
    }
};

int main() {
    Vector2D a(1, 2), b(3, 4);

    auto sum = a + b;                // member operator+: OK
    auto scaled = 2.0 * a;          // friend operator*: OK (2.0 on left)
    // auto scaled2 = a * 2.0;      // also works if you add that overload

    std::cout << sum << '\n';        // calls friend operator<<
    std::cout << a.length() << '\n'; // member function
}

Rule: if the left operand is not your class type (or you want symmetric treatment), make it a non-member friend. operator<< is the most common case — ostream is always on the left.


Friend Classes

A friend class gets access to all private and protected members of the grantor:

class Engine;  // forward declaration

class Car {
    int fuel_;
    int speed_;

    friend class Engine;  // Engine can access Car's private members

public:
    Car() : fuel_(100), speed_(0) {}
    int speed() const { return speed_; }
};

class Engine {
public:
    void accelerate(Car& car, int amount) {
        if (car.fuel_ > 0) {           // friend access — reads private fuel_
            car.speed_ += amount;      // friend access — modifies private speed_
            car.fuel_ -= amount / 10;  // friend access — modifies private fuel_
        }
    }

    int fuel(const Car& car) const {
        return car.fuel_;  // friend access
    }
};

int main() {
    Car car;
    Engine engine;

    engine.accelerate(car, 30);
    std::cout << "Speed: " << car.speed() << ", Fuel: " << engine.fuel(car) << '\n';
}

Friend classes represent a stronger coupling than friend functions. All methods of Engine can see all private members of Car. This is appropriate when the two classes are tightly coupled by design (e.g., iterator and container).


Friend Member Functions

You can grant friendship to a specific member function of another class (not the whole class):

class Logger;

class Server {
    int port_;
    std::string secret_key_;

    // Only Logger::logStatus can access Server's private members
    friend void Logger::logStatus(const Server&);

public:
    Server(int port) : port_(port), secret_key_("abc123") {}
};

class Logger {
public:
    void logStatus(const Server& s) {
        // Access to private members — but only for logStatus
        std::cout << "Port: " << s.port_ << '\n';
        // std::cout << s.secret_key_;  // OK here but we choose not to log it
    }

    void otherMethod(const Server& s) {
        // std::cout << s.port_;  // compile error — not a friend function
    }
};

This is more precise than making the whole class a friend, but requires Logger to be fully defined before Server.


Factory Functions Using Friend

Private constructors + friend factory is a pattern to control how objects are created:

class SecureToken {
    std::string value_;

    // Private constructor — cannot be called directly
    explicit SecureToken(std::string val) : value_(std::move(val)) {}

    friend SecureToken createToken(const std::string& key);

public:
    const std::string& value() const { return value_; }
};

SecureToken createToken(const std::string& key) {
    // Validate key, generate token, etc.
    if (key.empty()) throw std::invalid_argument("Key required");
    return SecureToken{"tok_" + key};  // friend — can call private constructor
}

int main() {
    // SecureToken t("raw");  // compile error — constructor is private
    auto token = createToken("mykey");  // must go through factory
    std::cout << token.value() << '\n';  // tok_mykey
}

Practical: Fraction with Arithmetic Operators

#include <numeric>
#include <stdexcept>
#include <iostream>

class Fraction {
    int num_, den_;

    void reduce() {
        int g = std::gcd(std::abs(num_), den_);
        num_ /= g;
        den_ /= g;
    }

public:
    Fraction(int num, int den) : num_(num), den_(den) {
        if (den_ == 0) throw std::invalid_argument("Denominator cannot be zero");
        if (den_ < 0) { num_ = -num_; den_ = -den_; }
        reduce();
    }

    // Arithmetic as non-member friends — symmetric, natural syntax
    friend Fraction operator+(const Fraction& a, const Fraction& b) {
        return {a.num_ * b.den_ + b.num_ * a.den_, a.den_ * b.den_};
    }

    friend Fraction operator*(const Fraction& a, const Fraction& b) {
        return {a.num_ * b.num_, a.den_ * b.den_};
    }

    friend bool operator==(const Fraction& a, const Fraction& b) {
        return a.num_ == b.num_ && a.den_ == b.den_;
    }

    friend std::ostream& operator<<(std::ostream& os, const Fraction& f) {
        if (f.den_ == 1) return os << f.num_;
        return os << f.num_ << '/' << f.den_;
    }
};

int main() {
    Fraction half(1, 2), third(1, 3);
    std::cout << half + third << '\n';   // 5/6
    std::cout << half * third << '\n';   // 1/6
    std::cout << (half == Fraction(2, 4)) << '\n';  // 1 (true — 1/2 == 2/4)
}

friend vs Getters

Both friend and public getters allow external code to read private data. They differ in scope:

friendPublic getter
Who can accessSpecific named functions/classesEveryone
Encapsulation impactLimited — known collaborators onlyLower — exposed to all
PerformanceDirect accessPossible indirection
Typical useOperators, tight collaboratorsGeneral API

Prefer public getters for data that any caller legitimately needs. Use friend when the collaboration is tight and you want to prevent the data from being accidentally accessed by everyone.


Common Mistakes

Over-friending: declaring many classes as friends defeats encapsulation. Keep the friend list to a minimum — usually just operator<< and one or two specific collaborators.

Assuming friendship is symmetric: if A declares B as a friend, B can access A’s private members. But A cannot access B’s private members unless B also declares A as a friend.

Assuming friendship is inherited:

class Base {
    int secret_;
    friend void readSecret(const Base& b);  // only for Base
};

class Derived : public Base {
    int derived_secret_;
};

void readSecret(const Base& b) {
    b.secret_;  // OK
}

void readSecret(const Derived& d) {
    // d.derived_secret_;  // ERROR — not a friend of Derived
    // static_cast<const Base&>(d).secret_;  // OK — accessing Base's member
}

Key Takeaways

  • friend grants a specific function or class access to private and protected members
  • operator<< almost always needs to be a non-member friend (stream is on the left)
  • Symmetric binary operators (+, -, *) benefit from non-member friend status — both a+b and b+a work naturally
  • Friend classes grant access to all methods — appropriate for tightly coupled pairs (iterator/container, builder/product)
  • Factory functions with private constructors use friend to control object creation
  • Friendship is not inherited and not symmetric
  • Prefer public getters for general access; use friend for specific named collaborators with a tight coupling justification

자주 묻는 질문 (FAQ)

Q. 이 내용을 실무에서 언제 쓰나요?

A. friend functions and classes grant access to private and protected members. When to use friend for operators and factori… 실무에서는 위 본문의 예제와 선택 가이드를 참고해 적용하면 됩니다.

Q. 선행으로 읽으면 좋은 글은?

A. 각 글 하단의 이전 글 또는 관련 글 링크를 따라가면 순서대로 배울 수 있습니다. C++ 시리즈 목차에서 전체 흐름을 확인할 수 있습니다.

Q. 더 깊이 공부하려면?

A. cppreference와 해당 라이브러리 공식 문서를 참고하세요. 글 말미의 참고 자료 링크도 활용하면 좋습니다.


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

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

  • [C++ Classes and Objects: Constructors, Access Control, and](/en/blog/cpp-class-object-beginner/
  • [C++ this Pointer: Chaining, const Methods, Lambdas, and CRTP](/en/blog/cpp-this-pointer/
  • [C++ Functions: Parameters, Return Values, Overloading, and](/en/blog/cpp-function-basics/

이 글에서 다루는 키워드 (관련 검색어)

C++, friend, access control, OOP, operators 등으로 검색하시면 이 글이 도움이 됩니다.