Friendship in C++

Encapsulation is a staple of object-oriented  programming: by hiding (or “abstracting away”) the implementation details of a class behind a public interface we can create abstract data types. Users of an encapsulated class need only be concerned about what operations that type supports and not about how things are actually implemented inside.

C++ supports encapsulation with access specifiers: the keywords public, protected and private are used inside a class definition to indicate which part of a program can access a particular member of a class. In general, members declared under the public access specifier are part of the interface for that type, and can be accessed from anywhere in a program; protected and private access specifiers restrict accessibility of members to derived classes and members of the class itself, respectively.

There are some cases though when we need to access the implementation details of a class from outside, for example when a non member function is part of the public interface of that class (a notable example being overloaded operator functions defined as non member functions). Friendship declarations let non member functions, member functions from other classes, or even entire classes access protected and private members of a class.

Friend functions, friend classes and friend member functions

A class can let other classes access its private and protected members by making those classes friends with a friend declaration inside the class definition:

// friendship.hpp 

class AClass
{
// friend declarations can be put anywhere in a class definition (no access control):
friend class AnotherClass;   // ordinary classes don't need forward declarations

public:
    AClass(int data) : data(data) {}
    int Get() { return data; }
private:
    int data;
};

class AnotherClass
{
public:
    AnotherClass(const AClass &a) : data(a.data) {} // can access private members of AClass
private:
    int data;
};

Friend declarations are not subject to access control, so they can be put anywhere in a class definition. It’s a good practice to put all friend declarations at the beginning or at the end of the class definition. Also note that in order to declare a class as a friend, that class dont’ need to be already visible.

Keep in mind that friendship works in one way only: a class that is granted friendship can access private and protected members of the class containing the friendship declaration, but not vice-versa (unlike the real world, in C++ friendship is not reciprocal).

A class can also declare a function as friend: as said, this is useful when a function is part of the class interface, so it needs to access the class implementation details, but it is not defined as a member function. Think about an overloaded << operator that writes to an output stream: the function is indeed part of the class interface, but it cannot be defined as a member function because the first operand must be a reference to an output stream (the first implicit parameter of an overloaded operator defined as a member function is the implicit “this” pointer). The function is then defined as a non member function and is declared a friend of the class, so it’s able to access the class implementation:

// friendship.hpp

#include <iostream>

class AClass
{
friend class AnotherClass;
friend std::ostream &operator<<(std::ostream&, const AClass&);   // friend declarations are not function declarations(declaration outside class is needed)
friend int AFriendFunction(const AClass &a) { return a.i * 2; }  // friend function can be defined inside the class (declaration outside class still needed)

public:
    AClass(int i) : data(data) {}
    int Get() { return data; }
private:
    int data;
};

// a declaration outside the class is needed
// (some compilers use friend declaration as forward declaration for function)

std::ostream &operator<<(std::ostream&, AClass const&);

int AFriendFunction(AClass const&);



// friendship.cpp

std::ostream &operator<<(std::ostream &os, const AClass &a)
{ 
    return os << a.i;   // can access private member of AClass
}

Friend function declaration are not declarations in the general sense, so a declaration for the function must be visible when the user calls the function. Some compilers don’t enforce this rule, and use the friend declaration as a function declaration, but is a good habit to put a declaration for friend functions, should we build our program with a more strict compiler.

As shown in the example above, friend function can even be defined inside the class: in this case it’s as if the function was defined as a non member function outside the class. A declaration outside the class is still needed though.

If we don’t want all members of a class to be declared as friends, we can also restrict access to one or more member functions of a class, by declaring a friend member function. To do so, a declaration for the member function must be in scope, so we must take care to order our code appropriately: first we need to forward declare the class granting friendship; then we provide the class definition for the class that contains the declaration (but not the definition) for the member function to be declared as friend; then we define the class granting friendship, and put the friend declaration there; last, in the implementation file, we define the friend member function:

// friendship.hpp

// 1 - forward declare AClass (for SomeClass::MemberFunction declaration)
class AClass;

// 2 - define SomeClass and declare (but not define) friend member function 
class SomeClass
{
public:
    int MemberFunction(AClass const &a);  // 3 - declare only - define in cpp file (after AClass)
};

class AClass
{
// ....other friend declarations

// 3 - declare friend member function of another class
friend int SomeClass::MemberFunction(const AClass&);

public: 
    AClass(int i) : data(data) {} 
    int Get() { return data; } private: int data;
private: 
    int data;
};


// friendship.cpp

// 4 - define friend member function in cpp file
int SomeClass::MemberFunction(const AClass &a)
{
    return 3 * a.i;
}

Friend classes and templates

As usual, when templates get into the mix things get a tad more complicated: a class granting friendship can be a template or not, and the class or function being granted friendship can be a template or not.

When a class template contains a friend declaration for an ordinary (non template) class, all istances of the class template make that class a friend. As usual, ordinary classes don’t need to be visible before being granted friendship:

// friendship.hpp 

template <typename T>
class AClassTemplate
{
// when an ordinary class is friend of a class template, the class is friend with all intances of the class template
friend class AnOrdinaryClass;              // ordinary classes don't need to be already visible

public:
    AClassTemplate(const T &t) : data(t) {}
    T Get() { return data; }
private:
    T data;
};

class AnOrdinaryClass
{
public:
    AnOrdinaryClass (int data) : data(data) {}
    template <typename T>
    T Sum(AClassTemplate<T> const &a) { return data + a.data; }  // can access private member (for every T)
private:
    int data;
};

When both classes are templates things are different: a declaration for the class template that is to be made a friend must be visible and the class template which contains the friendship declaration can choose which instantiation of the friend class template to declare as friend; even the class template parameters can partecipate in specifying the instance of the friend class template:

// friendship.hpp

// class templates must be visible before being granted friendship
template <typename T>
class AnotherClassTemplate;

template <typename T>
class AClassTemplate
{
// when a class template is friend, the class template granting friendship can specify which instantiation is friend
friend class AnotherClassTemplate<T>;    // class template instantiated with the same template parameter T is friend
friend class AnotherClassTemplate<int>;  // a particular instance can be friend

public:
    AClassTemplate(const T &t) : data(t) {}
    T Get() { return data; }
private:
    T data;
}

In this example, a class instantiated with int is friend, and a class instantiated with the same type argument as the granting class’ type parameter is a friend.

A class template can also choose to be friend with all instantiations of the friend class template (for every template arguments with which it can be instantiated) with a friend template declaration:

// friendship.hpp

// class templates must be visible before being granted frienship
template <typename T>
class AnotherClassTemplate;

template <typename T>
class AClassTemplate
{
// .... other friend declarations

// all instantiations of a class template can be made friends with all instances of this class template
template <typename U> friend class AClassTemplate;   // friend template (same as template <typename> friend class AClassTemplate - template parameter's name is optional)

public:
    template <typename U>     // generalized copy constructor
    AClassTemplate(const AClassTemplate<U> &other) : data(other.data) {}
    AClassTemplate(const T &t) : data(t) {}
    T Get() { return data; }
private:
    T data;
}

All instances of the friend class template are friends of every instance of the class template granting friendship. This, for example,  allows the definition of a copy constructor (often called generalized copy constructor) that accepts all instances of the same class template (of course, in this case, the data member must be implicitly convertible). A similar copy assignment operator could be defined as well. Note that the template parameter’s name inside the template parameter list of the friend template is optional.

This applies to ordinary (non template) classes as well: an ordinary class can grant friendship to a particular instance of a template class, or to all instantiations (with a friend template declaration).

Friend functions and templates

A class template can grant friendship to ordinary functions and function templates:

// friendship.hpp

template <typename U>
class AClassTemplate;

template <class T>
T AFriendFunctionTemplate(AClassTemplate<T> const&);

template <typename T>
class AClassTemplate
{
// .... other friend declarations

friend T AFriendFunctionTemplate<>(AClassTemplate<T> const&);             // specialization for T is friend (same as friend T AFriendFunction<T>(AClassTemplate<T> const&)) - cannot be a definition  
friend int AFriendFunction(AClassTemplate<int> const &) { return a.data}; // no angle brackets - ordinary (non template) function - can be a definition
friend T YetAnotherFriendFunction(AClassTemplate<T> const &a) { return a.data; }  // friend declaration uses template parameters - defines a non member function instantiated with the class template

public:
    AClassTemplate(const T &t) : data(t) {}
    T Get() { return data; }
private:
    T data;
}

template <typename T>
T AFriendFunctionTemplate(AClassTemplate<T> const &a)
{
    return a.data;
}

int AFriendFunction(AClassTemplate<int> const &);

There can be three cases:

  • the friend declaration declares a function name with angle brackets (can be qualified with the scope resolution operator :: or not): the friend function is a function template and the arguments in the angle brackets specify for which instantiation the declaration is valid. If the class granting friendship is a template, its template parameters can be used as template arguments to identify the instance to be made friend; the brackets can also be left empty if template arguments can be deduced. These friend declarations cannot be definitions. A declaration for function templates to be declared as friends must be visible.
  • the name of the function in the friend declaration has no angle brackets and isn’t qualified (doesn’t contain ::): the function never refers to a function template and if no matching ordinary function is already declared the friend declaration is a new declaration. These declarations can be definitions.
  • the name of the function in the friend declaration has no angle brackets but is qualified (contains ::): the name must refer to a previously declared function or function template (an ordinary function is preferred over a matching function template); such friend declarations cannot be definitions.

A member function of a class template can be made friend too (code must be ordered properly):

// friendship.hpp

template <typename U>
class AClassTemplate;

template <typename T>
class AnotherClassTemplate
{
public:
    T MemberFunction(AClassTemplate<T> const &a);
private:

};

template <typename T>
class AClassTemplate
{
// .... other friend declarations

friend T AnotherClassTemplate<T>::MemberFunction(AClassTemplate<T> const &);  // specific instance

public:
    AClassTemplate(const T &t) : data(t) {}
    T Get() { return data; }
private:
    T data;
};

template <typename T>
T AnotherClassTemplate<T>::MemberFunction(AClassTemplate<T> const &a)
{
    return a.data;
}

A class can grant friendship to all instantiation of a function template or to  every instantiation of a member function of a class template using a friend template declaration:

// friendship.hpp

template <typename U>
class AClassTemplate;

template <class T>
T AnotherFriendFunctionTemplate(AClassTemplate<T> const&);

template <typename T>
class AClassTemplate
{
// .... other friend declarations

template <typename U> friend U AnotherFriendFunctionTemplate(AClassTemplate<U> const &);
template <typename U> friend U AnotherClassTemplate<U>::MemberFunction(AClassTemplate<U> const &); 
 
public:
    AClassTemplate(const T &t) : data(t) {}
    T Get() { return data; }
private:
    T data;
}

template <class T>
T AnotherFriendFunctionTemplate(AClassTemplate<T> const &a)
{
    return a.data;
}

Friend templates can be used in ordinary classes too.

 

 

 

Leave a Reply

Your email address will not be published. Required fields are marked *