본문으로 건너뛰기 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

Frequently Asked Questions (FAQ)

Q. When would I use this in practice?

A. friend functions and classes grant access to private and protected members.

Q. What should I read before this?

A. Follow the previous article or related articles links at the bottom of each post to learn in sequence. See the C++ series index for the full picture.

Q. Where can I study this more deeply?

A. Check cppreference and the relevant library’s official documentation. The reference links at the end of the article are also worth using.


Other articles related to this topic.


Keywords Covered in This Article (Related Search Terms)

This article covers C++, friend, access control, OOP, operators.