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?

Once a class is declared as a friend, it can never be removed or its access restricted by any subsequent class definitions.
Friendship must be explicitly granted by a class and is neither inherited by derived classes nor transitive between classes.
A friend class can only access public and protected members but remains restricted from accessing private state variables.
Friendship is automatically inherited, meaning a friend of a base class is also a friend of all its derived classes.
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

Drag each definition into its matching concept slot, then click Submit. Tap × to return a placed card to the pool.

friend Function
drag a definition here…
friend Class
drag a definition here…
operator<<
drag a definition here…
Encapsulation
drag a definition here…

Definition Pool

Commonly overloaded as a friend to allow natural output syntax for custom objects.
The core OOP principle that friend declarations selectively bypass to provide special access.
A class where every member method is granted access to another class's private members.
An external function granted access to a class's private and protected members.

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?

One single copy; all 500 instances share the same memory location for that static variable.
None; static members are stored on the disk and only loaded into memory when explicitly accessed.
Five hundred copies; each object receives its own independent version of the static variable.
Zero until the first object is created, after which exactly one copy is allocated for each thread.
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?

Because static functions lack a 'this' pointer, meaning they have no association with a specific object instance.
Because non-static variables are automatically deleted from memory whenever a static function is invoked.
Because the C++ compiler restricts static functions to only accessing data stored in the CPU registers for speed.
Because static functions are executed by the operating system kernel rather than the application runtime.
  • 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++?

Non-virtual methods are the only types of functions that can be executed concurrently by multiple processor cores without locks.
Non-virtual methods bypass the stack entirely and execute directly within the CPU's instruction cache for maximum throughput.
Non-virtual methods are automatically compressed by the preprocessor to occupy significantly less space in the application binary.
Non-virtual methods utilize compile-time resolution (static binding), avoiding the runtime overhead of a virtual function table lookup.

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?

The 'Account::getType()' implementation, because non-virtual methods are resolved based on the pointer's declared type.
Neither; the compiler will fail to build the application due to an ambiguous method resolution error in the class hierarchy.
The 'Current::getType()' implementation, because the C++ runtime always prioritizes the actual object type at execution.
Both implementations will be called sequentially to ensure that the base class properties are correctly initialized before use.

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

Drag each definition into its matching concept slot, then click Submit. Tap × to return a placed card to the pool.

virtual Method
drag a definition here…
non-virtual Method
drag a definition here…
override Keyword
drag a definition here…
Virtual Destructor
drag a definition here…

Definition Pool

Uses static binding, where the method call is resolved at compile time based on the pointer type.
Provides compiler-verified documentation that a method replaces a virtual base class function.
Enables late binding, allowing the dynamic type of an object to determine which method executes.
Ensures the complete inheritance chain is cleaned up when deleting via a base pointer.
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?