5.7 Friends, Static & Non-Virtual

Friend Functions
Section titled “Friend Functions”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 classvoid 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(); // illegalInstead, 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 <<forstd::ostream).
Caution: Overuse of friend functions can reduce encapsulation and make code harder to follow. Use them sparingly.
Friend Classes
Section titled “Friend Classes”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.
🧩 Knowledge Check
Section titled “🧩 Knowledge Check”Which of the following describes the correct behaviour of friendship in C++ class hierarchies?
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.
Common Example of Friend Functions:
Section titled “Common Example of Friend Functions:”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 classostream& 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 EuroImportantly:
operator<<is not a member function ofAccount.- 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 inAccountis slightly complex, but once created, this makes it very easy to interact with the class to display the contents.
🧩 Knowledge Check
Section titled “🧩 Knowledge Check”Match Friendship Concepts
Static States of Classes
Section titled “Static States of Classes”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 memberint 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 EuroThis account is owned by: Johnaccount number: 123457 has balance: 12.7 EuroThis account is owned by: DerekHow Static Members Work
nextAccountNumberis shared across all objects of the class.- Each time an
Accountobject is created, itsaccountNumberis set fromnextAccountNumber, which is then incremented. - This ensures that every account object has a unique number, without needing to pass it to the constructor.
🧩 Knowledge Check
Section titled “🧩 Knowledge Check”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?
Defining Static Members
Static members must be:
- Declared in the class (with the
statickeyword). - Defined and initialised once outside the class (without
static).
Static Member Functions
Section titled “Static Member Functions”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
thispointer). - Cannot be declared
virtual, since they are unrelated to object instances.
🧩 Knowledge Check
Section titled “🧩 Knowledge Check”Why is a static member function unable to access non-static member variables directly?
Important Points
Section titled “Important Points”- 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.
Non-Virtual Methods
Section titled “Non-Virtual Methods”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.
🧩 Knowledge Check
Section titled “🧩 Knowledge Check”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.
Virtual Example (nonVirtualTest.cpp)
Section titled “Virtual Example (nonVirtualTest.cpp)”#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 AccountPointer b: Current AccountPointer c: Deposit AccountBecause 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.
Non-Virtual Example (nonVirtualTest2.cpp)
Section titled “Non-Virtual Example (nonVirtualTest2.cpp)”#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 AccountPointer b: Generic AccountPointer c: Generic AccountBecause getType() is non-virtual, the method associated with the pointer’s static type (Account*) is always called.
🧩 Knowledge Check
Section titled “🧩 Knowledge Check”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?
Virtual Destructors
Section titled “Virtual Destructors”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.
Important Points:
Section titled “Important Points:”- 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.
🧩 Knowledge Check
Section titled “🧩 Knowledge Check”Match Virtual and Static Concepts
What happens when you delete a derived class object through a base class pointer if the base class destructor is NOT virtual?
© 2026 Derek Molloy, Dublin City University. All rights reserved.