본문 바로가기

Programming/C++ Basic

[Basic C++] 18 - Data Structure in C++ : Friend

1. Definition of "friend"

There is a very convenient keyword in C++ OOP that you can give special permission to a target entity to access to another class, even if the entity is not inside the class. Only the class implementer can declare who its friends are but not vice versa. 

 

Figure 1 C++ friend class

You can declare a friend function before its declaration because the compiler assumes as if the function had been declared using the "extern" keyword. This means that functions declared as friends are considered as they are declared in the global scope. Thus, functions declared in the local scope, member functions, are not allowed to be declared as friends before their complete class declaration. 

 

Figure 2 C++ friend class


2. Friend class and function

2.1) Friend class

A declaration of a friend class is the same as a friend-function but the prototype of a target class that accepts the friend class must be declared first. A friend class can access every class member of the other class even private and protected members.

 

Code example 1 - Declaration of friends entity

#include <iostream>
// Class prototype
class original;

// Friend class definition
class non_member_class
{
private:
	int pri_varn = 100;
public:
	int pub_varn = 200;
	void print_friend(original& o);
};

// Class full definition
class original
{
private:
	int pri_varo = 10;
public:
	int pub_varo = 20;
	friend class non_member_class;
};

// function definition
void non_member_class::print_friend(original& o)
{
	std::cout << o.pri_varo<< std::endl;
	std::cout << o.pub_varo << std::endl;
}

int main()
{
	non_member_class n_obj;
	original o_obj;
	n_obj.print_friend(o_obj);
}

Figure 1 Result of code example 1

The preceding code is a simple example that shows how to use the "friend" keyword using two classes. "Original" class declares "non-member-class" as a friend within its public scope. Due to the friend relationship, now "non-member-class" can access any class members in "Original" class. The function "print_friend" is declared inside "non-member-class" to print the class members of "Original" class.

 

The class prototype "class Original" and the function prototype "void print_friend(original& o)" are used to give the compiler a notion that they will be fully defined later. If we did not use the prototypes, a few errors would be popped up. For the same reason, the below code raises an error.

 

Code example 2 

[CODE]
// class original 
class non_member_class
{
private:
	int pri_varn = 100;
public:
	int pub_varn = 200;
	void print_friend(original& o)
	{
		std::cout << o.pri_varo << std::endl;
		std::cout << o.pub_varo << std::endl;
	};

};

[RESULT]
// ERROR
error C2027: use of undefined type 'original'

C2027 occurs since the "Original" class is not fully defined when the "print_friend" function attempts to call an object of the class.

 

2.2) Friend function

A function can be a friend of the other class and it can access any class members in the class. The full definition of the function must exist in the global scope or the same namespace.

 

Code example 3

#include <iostream>
class A
{
	// The compiler assumes the function is declared in the global scope or with the extern.
	friend void print_members(A& a_obj);
private:
	int pri = 10;
public:
	int pub = 20;
};

// A global function
void print_members(A& a_obj)
{
	std::cout << "Taget class's private variable = "<< a_obj.pri << std::endl;
	std::cout << "Taget class's public variable = " << a_obj.pub << std::endl;
}

int main()
{
	A a;
	print_members(a);
}

Figure 3 Result of code example 3

 

3. Other things to know

There are two most common mistakes you need to be careful when using the "friend" keyword. First of all, the friend entity of a given class is not a friend entity of the derived class of the given class unless explicitly stated. Second of all, a friend of a friend is not a friend unless explicitly stated. In other words, friend entities are not transitive.

 

3.1) Inheritance

Figure 4 shows friendships among the classes and a function. Class B inherits class A and class A has a friend function "function_global". You might guess that class B can be accessed through "function_global", as it's a friend of class A (base class). However, this kind of logical progression does not work in C++. Friendship always needs to be explicitly declared for each entity. 

 

 

 

Figure 4 Friend with inheritance

 

Code example 4

#include <iostream>
#include <string>
using std::string;
class A
{
private:
	string a_pri = "Private variable in A";
public:
	string a_pub = "Public variable in A";
	friend void function_global(A& a_obj);
};

class B : public A
{
private:
	string b_pri = "Private variable in B";
public:
	string b_pub = "Public variable in B";
};

void function_global(A& a_obj)
{
	std::cout << a_obj.a_pri << std::endl;
	std::cout << a_obj.a_pub << std::endl;
}

int main()
{
	A a;
	B b;
	function_global(a);
	function_global(b);
}

 

Figure 5 Result of code example 4

Classs A object and class B objects are passed to the "function_global"; however, the results show that the function only accesses to the variables in class A. 

 

3.2) Friend entity is not transitive

Code example 5 shows a complicated relationship between three entities; class A, class B, and a global function. The "function_global" is a friend of the "class A", and the "class A" is a friend of "class B". In the real world, a friend of a friend can be a friend too. However, the same logic does not work in C++. Thus to access the class members of the "class B" using the "function_global", you have to declare that "function_global" is a friend of "class B" explicitly.

 

Code example 5

#include <iostream>
class B;
class A
{
private:
    int A_a = 10;
public:
    int A_aa = 100;
    // Function prototype
    void function_in_A(B& b_obj);
};

class B
{
private:
    int B_b = 20;
public:
    int B_bb = 200;
    friend A;
    // Function prototype
    friend void function_global(B& b_obj);
};

void function_global(A& b_obj)
{
    std::cout << "Global function is the friend of class B : " << b_obj.B_b << std::endl;
    std::cout << "Global function is the friend of class B : " << b_obj.B_bb << std::endl;
}

void A::function_in_A(B& b_obj)
{
    std::cout << "member function in A is the friend of class B : " << b_obj.B_b << std::endl;
    std::cout << "member function in A is the friend of class B : " << b_obj.B_bb << std::endl;
}

int main()
{
    A a;
    B b;
    a.function_in_A(b);
    function_global(b);
}

Figure 6 Result of the preceding example

 

Figure 7 Schematic of the preceding example

If you try to access the "class B" from the "function_global", the C2039 error arises, as the "functiion_globa" is not a friend of the "class B".

[CODE]
// Try to call the object b through the function_global
function_global(b);

[RESULT]
error C2039: 'B_b': is not a member of 'A'
message : see declaration of 'A'
error C2039: 'B_bb': is not a member of 'A'
message : see declaration of 'A'

 


REFERENCES

[1] https://docs.microsoft.com/ja-jp/cpp/cpp/friend-cpp?view=vs-2019