What is a Friend?
A friend declaration appears within a class and grants a function or another class access to the private and protected members of the class that declares the friend (https://en.cppreference.com/w/cpp/language/friend.html).
Points to note:
- Friend declarations are made within the class, but their specific location is not restricted and is not subject to the access control level of the enclosing area (i.e., public, private, or protected). Generally, it is best to concentrate friend declarations at the beginning or end of the class definition.
- Friends can be functions (including member functions of the class) or classes.
- Friends can access the private and protected members of the class that declares them as friends.
- Friendship is not inherited, not transitive, and not reciprocal (friendship isn’t inherited, transitive, or reciprocal). This can be explained as follows:
- Not inherited: Unless explicitly declared, a derived class of my friend will not automatically become my friend.
- Not transitive: Unless explicitly declared, my friend’s friend will not automatically become my friend.
- Not reciprocal: Unless explicitly declared, I will not automatically become my friend’s friend.
A simple example of a friend:
#include <iostream>
using namespace std;
// Forward declaration of class for friend member function declaration
class MyClass;
// Declaration of a class whose member function will be a friend
class FriendClassFunction {
public:
void accessPrivateAndProtected(MyClass& obj); // Member function accessing MyClass's private and protected data
};
// Declaration of a class, making the class a friend
class FriendClass2 {
public:
void accessPrivateAndProtected(MyClass& obj); // Member function accessing MyClass's private and protected data
};
// Main class definition
class MyClass {
private:
int privateData;
protected:
int protectedData;
public:
MyClass(int priv, int prot) : privateData(priv), protectedData(prot) {}
// Declare friend function (normal global function)
friend void friendFunction(MyClass& obj);
// Declare specific member function as friend (FriendClass's member function)
friend void FriendClassFunction::accessPrivateAndProtected(MyClass& obj);
// Declare friend class (all member functions of FriendClass can access private and protected members)
friend class FriendClass2;
};
// Friend function definition (non-member function)
void friendFunction(MyClass& obj) {
cout << "friendFunction access private: " << obj.privateData << endl;
cout << "friendFunction access protected: " << obj.protectedData << endl;
}
// Definition of FriendClass member function
void FriendClassFunction::accessPrivateAndProtected(MyClass& obj) {
cout << "FriendClassFunction::accessPrivateAndProtected access private: " << obj.privateData << endl;
cout << "FriendClassFunction::accessPrivateAndProtected access protected: " << obj.protectedData << endl;
}
// Definition of FriendClass member function
void FriendClass2::accessPrivateAndProtected(MyClass& obj) {
cout << "FriendClass2 access private: " << obj.privateData << endl;
cout << "FriendClass2 access protected: " << obj.protectedData << endl;
}
int main() {
MyClass obj(42, 100);
// Call friend function
friendFunction(obj);
// Call class member function
FriendClassFunction fcf;
fcf.accessPrivateAndProtected(obj);
// Call member function of friend class
FriendClass2 fc2;
fc2.accessPrivateAndProtected(obj);
return 0;
}
/*
Compilation command: g++ -std=c++11 simple_friend.cpp -o simple_friend
Operating System: centos7.6
gcc version: 4.8.5
Output:
friendFunction access private: 42
friendFunction access protected: 100
FriendClassFunction::accessPrivateAndProtected access private: 42
FriendClassFunction::accessPrivateAndProtected access protected: 100
FriendClass2 access private: 42
FriendClass2 access protected: 100
*/
Does Friendship Violate Encapsulation?
This perspective mainly comes from the FAQ on the C++ standard official website (https://isocpp.org/wiki/faq/friends).
The conclusion given here is: No.
- “Friend” is an explicit mechanism for granting access, just like members. The control over who becomes a friend is entirely in the hands of the developer, and no other class or function can be granted access to the class without modifying the class source code (unlike public members, where users can gain access without modifying the class source code).
- Friend functions can be seen as a syntactic variant of public member functions of the class, or one can try to view friend functions as part of the public interface of the class. Clearly, friend functions do not violate encapsulation more than public functions of the class. When it is necessary to provide a way to access private and protected members to the outside world, changing members to public access is undoubtedly the worst practice. Using friends at least does not violate encapsulation more than using public member functions (like get()/set()).
What are the Advantages and Disadvantages of Friends Compared to Public Member Functions?
This perspective mainly comes from the FAQ on the C++ standard official website (https://isocpp.org/wiki/faq/friends).
As mentioned earlier, friend functions can be seen as a syntactic variant of public member functions of the class. So what are the advantages and disadvantages of friends compared to public member functions?
-
Advantages: They provide a degree of freedom in interface design options. The calling style of friend functions is similar to f(x), while the calling style of member functions is similar to x.f(). Therefore, being able to choose between member functions (x.f()) and friend functions (f(x)) allows designers to select the most readable syntax, thereby reducing maintenance costs.
-
Disadvantages: Friend functions cannot be virtual functions, thus cannot directly implement dynamic binding. When dynamic binding is needed, additional code is required, typically declaring a protected virtual function. Here’s an example:
-
class Base { public: friend void f(Base& b); // ... protected: virtual void do_f(); // protected virtual function, called by virtual function, implements polymorphism // ... }; inline void f(Base& b) { b.do_f(); // If b is actually an object of Derived class, then call Derived::do_f(); while f is always a friend of Base class, not a friend of Derived class. If it were a public member function, a virtual function could be implemented directly. } class Derived : public Base { public: // ... protected: virtual void do_f(); // Override f(Base& b) behavior // ... }; void userCode(Base& b) { f(b); }
When to Use Member Functions? When to Declare Friend Functions?
This perspective mainly comes from the FAQ on the C++ standard official website (https://isocpp.org/wiki/faq/friends).
Try to use member functions, and use friends when necessary. For example, friend functions have advantages in the following scenarios:
Member functions of a class have an implicit first parameter—<span>this</span> pointer, while friend functions do not; all parameters must be explicitly declared. This is particularly useful in operator overloading, especially when ensuring the commutativity of operations. For example, when wanting to overload the multiplication operator <span>*</span> so that a <span>Complex</span> object can be multiplied by a <span>double</span> and vice versa. Overloading the operator as a member function, <span>double * complex</span> cannot be directly implemented; however, overloading the operator as a friend function allows for free definition of the order of the two parameters.
-
/* Using member functions, cannot implement double * complex */ #include <iostream> using namespace std; class Complex { private: double real; double imag; public: Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) {} // Member function overload operator: c * 2.5 is valid Complex operator*(const double& d) const { return Complex(real * d, imag * d); } void display() const { cout << "(" << real << ", " << imag << "i)" << endl; } }; int main() { Complex c1(3.0, 4.0); Complex c2 = c1 * 2.5; // Valid: equivalent to c1.operator*(2.5) c2.display(); // Output (7.5, 10i) // Complex c3 = 2.5 * c1; // Error! 2.5.operator*(c1) is not valid return 0; } /* Compilation command: g++ -std=c++11 complex_multi_member.cpp -o complex_multi_member Operating System: centos7.6 gcc version: 4.8.5 Output: (7.5, 10i) */ -
/* Using friends, can implement double * complex */ #include <iostream> using namespace std; class Complex { private: double real; double imag; public: Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) {} // Declare friend function. Note, both parameters need to be explicitly listed. friend Complex operator*(const Complex& c, const double& d); friend Complex operator*(const double& d, const Complex& c); void display() const { cout << "(" << real << ", " << imag << "i)" << endl; } }; // Define friend function: Complex * double Complex operator*(const Complex& c, const double& d) { return Complex(c.real * d, c.imag * d); } // Define another friend function: double * Complex // Note here double is the first parameter, Complex is the second parameter. // This is something that member functions cannot implement. Complex operator*(const double& d, const Complex& c) { // Utilizing the commutativity of multiplication, directly call the previous function return c * d; } int main() { Complex c1(3.0, 4.0); Complex c2 = c1 * 2.5; // Valid: calls operator*(c1, 2.5) c2.display(); // Output (7.5, 10i) Complex c3 = 2.5 * c1; // Valid: calls operator*(2.5, c1) c3.display(); // Output (7.5, 10i) return 0; } /* Compilation command: g++ -std=c++11 complex_multi_friend.cpp -o complex_multi_friend Operating System: centos7.6 gcc version: 4.8.5 Output: (7.5, 10i) (7.5, 10i) */
In the book Effective C++ (Chinese Edition) (3rd Edition), there is a similar example, which suggests that the above example should be implemented as:
/*
Using non-friend non-member functions, can implement double * complex
*/
#include <iostream>
using namespace std;
class Complex {
private:
double real;
double imag;
public:
Complex(double r = 0.0, double i = 0.0) : real(r), imag(i) {}
void display() const {
cout << "(" << real << ", " << imag << "i)" << endl;
}
double getReal() const { return real; }
double getImag() const { return imag; }
};
// Define non-member non-friend function: Complex * double
Complex operator*(const Complex& c, const double& d) {
return Complex(c.getReal() * d, c.getImag() * d);
}
// Define another non-member non-friend function: double * Complex
// Note here double is the first parameter, Complex is the second parameter.
// This is something that member functions cannot implement.
Complex operator*(const double& d, const Complex& c) {
// Utilizing the commutativity of multiplication, directly call the previous function
return c * d;
}
int main() {
Complex c1(3.0, 4.0);
Complex c2 = c1 * 2.5; // Valid: calls operator*(c1, 2.5)
c2.display(); // Output (7.5, 10i)
Complex c3 = 2.5 * c1; // Valid: calls operator*(2.5, c1)
c3.display(); // Output (7.5, 10i)
return 0;
}
/*
Compilation command: g++ -std=c++11 complex_multi_friend_effective.cpp -o complex_multi_friend_effective
Operating System: centos7.6
gcc version: 4.8.5
Output:
(7.5, 10i)
(7.5, 10i)
*/
In the Effective C++ (Chinese Edition) (3rd Edition), it is recommended not to use friend. However, it uses public member functions, and according to the previous perspective, using friends does not violate encapsulation more than using public member functions (like get()/set()). Therefore, this implementation does not have better encapsulation.
As for what approach to adopt in specific implementations, it is a matter of personal preference. However, I feel that in most cases, whether to use friends or member functions should not be the main concern, as neither will have a significant impact on the program, such as causing slow compilation speed or making it difficult to locate runtime issues. It may be better to focus energy on areas that have a greater impact on the program.
Examples of Friend Usage
- Operator overloading (e.g., <<, >>), in which case providing a public interface is also possible.
- Unit tests accessing private members, in which case providing a public interface or modifying the testing scheme should also be possible.
- CRTP (Curiously Recurring Template Pattern) to implement the singleton pattern. This will be introduced in detail later.
CRTP (Curiously Recurring Template Pattern) is a C++ template programming technique where a class inherits from a template base class that takes itself as a template parameter to achieve static polymorphism. Its basic form is:
template <typename Derived>
class Base {
// Base class implementation
};
class Derived : public Base<Derived> {
// Derived class implementation
};
Using CRTP to implement the singleton pattern, a simple test code is as follows:
#include <iostream>
template <typename T>
class Singleton {
public:
// Delete copy constructor and assignment operator to ensure singleton uniqueness
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
// Global access point, returns a reference to the unique instance
static T& GetInstance() {
static T instance; // C++11 guarantees thread-safe initialization of static local variables
return instance;
}
protected:
Singleton() = default; // Constructor is protected to prevent external instantiation
virtual ~Singleton() = default; // Virtual destructor to ensure correct destruction
};
// Specific singleton class, e.g., a log manager
class Logger : public Singleton<Logger> {
// Declare friend, allowing Singleton<Logger> to access Logger's private constructor
friend class Singleton<Logger>;
private:
Logger() { std::cout << "Logger instance created.\n"; } // Private constructor
~Logger() override = default;
public:
void log(const std::string& message) {
std::cout << "Log: " << message << std::endl;
}
};
// Usage example
int main() {
Logger& logger1 = Logger::GetInstance();
logger1.log("First message");
Logger& logger2 = Logger::GetInstance(); // logger2 and logger1 are the same instance
logger2.log("Second message");
// Verify they are the same instance
std::cout << "Are they the same instance? " << (&logger1 == &logger2 ? "Yes" : "No") << std::endl; // Output Yes
return 0;
}
/*
Compilation command: g++ -std=c++11 crtp_singleton.cpp -o crtp_singleton
Operating System: centos7.6
gcc version: 4.8.5
Output:
Log: First message
Log: Second message
Are they the same instance? Yes
*/
Using CRTP to implement the singleton pattern has the following advantages when there are multiple classes that need to be singletons:
- Any class that needs to be a singleton only needs to inherit from
<span>Singleton<T></span>to automatically gain all singleton capabilities, reducing code duplication. - Ensures consistent singleton implementation logic across all instances.
And here, friends are used; I have not yet found a better alternative that does not use friends.
References
-
Assistance from Tencent Yuanbao-deepseek (https://yuanbao.tencent.com/) and the deepseek official website (https://www.deepseek.com/).
-
https://en.cppreference.com/w/cpp/language/friend.html
-
C++ Primer (Chinese Edition) 5th Edition, Section 7.2.1 Friends; Section 7.3.4 Friends Revisited.
-
C++ FAQ (Frequently Asked Questions): https://isocpp.org/wiki/faq/friends. This is the main source of many perspectives.
-
C++ FQA (frequently questioned answers): https://yosefk.com/c++fqa/friend.html#fqa-14.2. To be honest, after looking at it, I feel it is not very useful. I have also seen criticisms of this part under related questions on StackOverflow.
-
https://stackoverflow.com/questions/17434/when-should-you-use-friend-in-c