Skip to content

Search is only available in production builds. Try building and previewing the site to test it out locally.

5.7 Friends, Static & Non-Virtual

In C++, access control is normally determined by three keywords:

  • private – accessible only within the class.
  • protected – accessible within the class and its derived classes.
  • public – accessible from anywhere.

A class can, however, explicitly grant access to an external function or another class by declaring it as a friend. Friendship must be granted; it cannot be acquired.

Example: A Friend Function

class SomeClass {
private:
int x;
friend void someFunction(SomeClass&); // declare friend
public:
// public interface
};
// Defined outside the class
void someFunction(SomeClass& a) {
a.x = 5; // allowed because it is a friend
}

Here, someFunction() is not a member of SomeClass. It does not have scope inside the class, so you cannot call it like this:

SomeClass b;
b.someFunction(); // illegal

Instead, it is just a normal function with special access rights.

Why Use Friend Functions?

  • They avoid cluttering the public interface with methods that are only needed in special cases.
  • They allow direct access to private or protected states without making them public.
  • They are often used when overloading operators (e.g., operator << for std::ostream).

Caution: Overuse of friend functions can reduce encapsulation and make code harder to follow. Use them sparingly.

You can also declare an entire class as a friend:

class A {
private:
int x;
friend class B; // all methods of B are friends
};

In this case, every method of B can access the private members of A.

Important Rules of Friendship

  • Friendship is not inherited:
class B {
void f(A& a) { a.x++; } // allowed
};
class C : public B {
void g(A& a) { a.x++; } // illegal, C is not a friend of A
};
  • Friendship is not transitive:
class AA {
friend class BB;
};
class BB {
friend class CC;
};
class CC {
// not a friend of AA
};

In other words, just because BB is a friend of both AA and CC does not make CC a friend of AA.

Quiz
Select 0/1

Which of the following describes the correct behaviour of friendship in C++ class hierarchies?

Code Cloze
C++

Declaring a Friend Function

  • Friend functions (or classes) break normal access control rules, but only where explicitly granted.
  • They are useful in limited circumstances, such as operator overloading or tightly coupled classes.
  • Friendship is not inherited, not transitive, and should be used with care to preserve encapsulation.

One of the most common uses of friend functions is to overload operators that need access to private data, especially the stream insertion operator (<<) for output. See the example friendOperatorExample.cpp

Example: operator << as a Friend \n

#include <iostream>
#include <string>
using namespace std;
class Account {
protected:
int accountNumber;
float balance;
string owner;
public:
Account(string owner, float balance, int accountNumber)
: accountNumber(accountNumber), balance(balance), owner(owner) {}
// Declare operator<< as a friend
friend ostream& operator<<(ostream& os, const Account& acc);
};
// Friend function defined outside the class
ostream& operator<<(ostream& os, const Account& acc) {
os << "Account number: " << acc.accountNumber
<< ", Owner: " << acc.owner
<< ", Balance: " << acc.balance << " Euro";
return os;
}
int main() {
Account a("Derek Molloy", 250.75f, 123456);
cout << a << "\n"; // uses friend operator<<
return 0;
}

This code gives the output:

Account number: 123456, Owner: Derek Molloy, Balance: 250.75 Euro

Importantly:

  • operator<< is not a member function of Account.
  • Declaring it as a friend allows direct access to protected data (accountNumber, balance, owner).
  • This makes it possible to use the natural cout << a; syntax, which is very useful. The operator notation in Account is slightly complex, but once created, this makes it very easy to interact with the class to display the contents.
Concept Match

Match Friendship Concepts

Normally, each instance of a class has its own copy of member variables (states). However, C++ also allows us to define static states, which are shared by all instances of the class. You can think of a static variable as belonging to the class itself, rather than to any single object.

Example: Static State in the Account Class

#include <iostream>
#include <string>
using namespace std;
class Account {
protected:
static int nextAccountNumber; // shared across all Accounts
int accountNumber;
float balance;
string owner;
public:
// Delegating constructors (C++11)
Account(string owner, float aBalance)
: balance(aBalance), owner(owner), accountNumber(nextAccountNumber++) {}
Account(float aBalance)
: Account("Not Defined", aBalance) {} // delegates to main constructor
// Member functions
virtual void display() const {
cout << "account number: " << accountNumber
<< " has balance: " << balance << " Euro" << "\n";
cout << "This account is owned by: " << owner << "\n";
}
virtual void makeLodgement(float amount) { balance += amount; }
virtual void makeWithdrawal(float amount) { balance -= amount; }
virtual ~Account() = default; // best practice for base classes
};
// Definition of static member
int Account::nextAccountNumber = 123456;
int main() {
Account a("John", 10.50f);
Account b("Derek", 12.70f);
a.display();
b.display();
return 0;
}

This code gives the output:

account number: 123456 has balance: 10.5 Euro
This account is owned by: John
account number: 123457 has balance: 12.7 Euro
This account is owned by: Derek

How Static Members Work

  • nextAccountNumber is shared across all objects of the class.
  • Each time an Account object is created, its accountNumber is set from nextAccountNumber, which is then incremented.
  • This ensures that every account object has a unique number, without needing to pass it to the constructor.
Quiz
Select 0/1

If a class contains a static data member and 500 individual objects of that class are instantiated, how many copies of that static member exist in memory?

Code Cloze
C++

Defining Static Members

Static members must be:

  • Declared in the class (with the static keyword).
  • Defined and initialised once outside the class (without static).

Just like variables, functions can also be declared static:

class Account {
public:
static int getNextAccountNumber(); // static method
};

Static methods:

  • Belong to the class, not an object.
  • Cannot access non-static member variables (they have no this pointer).
  • Cannot be declared virtual, since they are unrelated to object instances.
Quiz
Select 0/1

Why is a static member function unable to access non-static member variables directly?

  • Static variables are shared by all objects of a class.
  • They can have public, private, or protected access.
  • Static functions allow class-level operations but cannot access instance-level states.
  • A common use case is for unique IDs or counters across all instances of a class.

By default, methods in C++ are non-virtual. Omitting the virtual keyword declares a method as non-virtual, which means it cannot be overridden polymorphically in derived classes.

Declaring a method as virtual enables overriding (dynamic dispatch, or late binding), whereas declaring it as non-virtual disables this behaviour.

Consider the following example:

theAccount->display();

The method that executes depends on whether theAccount points to an Account, a DepositAccount, or a CurrentAccount. If display() is virtual, the method belonging to the object’s dynamic type will be called. If it is non-virtual, the method belonging to the static type (the type of the pointer) will be called.

Why Non-Virtual Methods Exist:

  • In classes that will not have derived classes, using non-virtual methods avoids the small runtime overhead of virtual function dispatch.
  • Declaring a method non-virtual ensures it cannot be overridden, which can be useful for constructors and in situations where behaviour must remain fixed.
  • Non-virtual calls are slightly faster because the compiler can resolve the method call at compile time.
Quiz
Select 0/1

What is the primary performance advantage of using non-virtual methods instead of virtual methods in C++?

Most object-oriented languages default to virtual methods, but in C++ the default is non-virtual.

#include<iostream>
using namespace std;
class Account {
public:
virtual string getType() { return "Generic Account"; }
};
class Current : public Account {
public:
string getType() override { return "Current Account"; }
};
class Deposit : public Account {
public:
string getType() override { return "Deposit Account"; }
};
int main() {
Account* a = new Account();
Account* b = new Current();
Account* c = new Deposit();
cout << "Pointer a: " << a->getType() << "\n";
cout << "Pointer b: " << b->getType() << "\n";
cout << "Pointer c: " << c->getType() << "\n";
}

Output:

Pointer a: Generic Account
Pointer b: Current Account
Pointer c: Deposit Account

Because getType() is virtual, the method most closely associated with the object’s dynamic type is called.

The override keyword in C++ is used when a derived class method is intended to override a virtual method from a base class. It was introduced in C++11 to make code safer and clearer. Without override, if you accidentally mis-type a method signature in the derived class, the compiler silently treats it as a new method, not as an override of the base class method. This can lead to subtle bugs.

The addition of override also makes code self-documenting as readers know immediately that the method is overriding a base virtual method.

#include<iostream>
using namespace std;
class Account {
public:
string getType() { return "Generic Account"; }
};
class Current : public Account {
public:
string getType() { return "Current Account"; }
};
class Deposit : public Account {
public:
string getType() { return "Deposit Account"; }
};
int main() {
Account* a = new Account();
Account* b = new Current();
Account* c = new Deposit();
cout << "Pointer a: " << a->getType() << "\n";
cout << "Pointer b: " << b->getType() << "\n";
cout << "Pointer c: " << c->getType() << "\n";
}

Output:

Pointer a: Generic Account
Pointer b: Generic Account
Pointer c: Generic Account

Because getType() is non-virtual, the method associated with the pointer’s static type (Account*) is always called.

Quiz
Select 0/1

If a pointer's static type is 'Account*' but it points to a 'Current' object, and 'getType()' is NOT virtual, which implementation will be executed?

One special case is destructors:

  • Destructors in a base class should always be declared virtual.
  • This ensures the destructor of the correct derived type is called when deleting objects via a base class pointer.

For example:

class Account {
public:
virtual ~Account() {}
};
class Deposit : public Account {
public:
~Deposit() override {
// calculate interest
// send statement
}
};

If the destructor were non-virtual, deleting a Deposit object through an Account* would only call the base destructor, leaking resources or skipping derived clean-up.

  • Virtual methods allow overriding; resolved at runtime (dynamic binding).
  • Non-virtual methods have fixed behaviour; resolved at compile time (static binding).
  • Destructors should always be virtual in base classes intended for polymorphic use.
  • Use non-virtual methods for final, fixed behaviour or when no derived classes are expected.
Concept Match

Match Virtual and Static Concepts

Quiz
Select 0/1

What happens when you delete a derived class object through a base class pointer if the base class destructor is NOT virtual?