Question

When to Use Forward Declarations in C++

cppforward-declarationc++-faq

Question

I want to understand exactly when I am allowed to use a forward declaration of a class in another class's header file.

For example, can I use a forward declaration for:

  • a base class,
  • a class stored as a data member,
  • a class passed to a member function by reference,
  • or other similar cases?

When is a forward declaration enough, and when is the full class definition required?

Short Answer

By the end of this page, you will understand what a forward declaration is in C++, when it is sufficient, and when the compiler needs the complete class definition instead. You will also learn practical rules for inheritance, member variables, pointers, references, function parameters, and return types.

Concept

A forward declaration tells the compiler that a type exists, without giving its full definition yet.

class Engine;

This means: “There is a class named Engine. You will see its full definition later.”

This is useful because C++ compilation depends heavily on header files. Including too many headers can:

  • slow down compilation,
  • create circular include problems,
  • increase coupling between parts of your program.

A forward declaration is enough when the compiler only needs to know that a type exists. A full definition is required when the compiler needs to know the type's size, layout, or members.

Core rule

A forward declaration is enough when you only use the type indirectly, such as through:

  • pointers,
  • references,
  • declarations of functions that take or return the type by reference or pointer.

A full class definition is required when you use the type directly, such as:

  • inheriting from it,
  • storing it by value as a member,
  • calling its member functions in code that must be compiled now,
  • creating objects of that type,
  • using sizeof on it.

Why this matters

Forward declarations help keep headers lightweight and reduce dependencies. In real projects, this improves build times and makes code easier to maintain.

However, using them in the wrong place causes compilation errors because the compiler cannot work with an incomplete type where a complete type is required.

Mental Model

Think of a forward declaration like seeing a person's name on a guest list without knowing anything else about them.

  • If you only need to know that the person exists, the name is enough.
  • If you need their full profile—height, address, or job details—you need the complete record.

In C++:

  • A pointer or reference is like keeping someone's phone number or ID tag. You do not need their full details.
  • A member object by value is like reserving physical space for them in a room. You must know exactly how big they are.
  • A base class is part of the derived class's structure, so the compiler must know the full definition.
Take Quiz

Syntax and Examples

Basic forward declaration

class Engine;

class Car {
public:
    void setEngine(Engine& e);

private:
    Engine* engine;
};

This works because:

  • Engine* is a pointer,
  • Engine& is a reference,
  • the compiler does not need to know the full size of Engine here.

When a full definition is required

class Engine;

class Car {
private:
    Engine engine; // Error: Engine is incomplete here
};

This fails because Car contains an Engine object directly. The compiler must know:

  • how much memory Engine needs,
  • how to lay out Car in memory.

Base class example

class Vehicle;

class Car : public Vehicle { // Error: Vehicle must be fully defined
};

This does not work with only a forward declaration. A base class must be fully defined before the derived class is declared.

Valid use in a header + source split

Car.h

class Engine;

class Car {
public:
    void start(Engine& e);

private:
    Engine* engine;
};

Car.cpp

#include "Car.h"
#include "Engine.h"

void Car::start(Engine& e) {
    engine = &e;
}

Here, the header uses only a forward declaration, and the .cpp file includes the full definition when needed.

Step by Step Execution

Consider this example:

class Printer;

class Document {
public:
    void print(Printer& p);

private:
    Printer* printer;
};

Step by step:

  1. class Printer;

    • The compiler learns that a type named Printer exists.
    • It still does not know the size or members of Printer.
  2. void print(Printer& p);

    • This is valid.
    • A reference parameter only needs the type name to exist.
  3. Printer* printer;

    • This is also valid.
    • A pointer has a known size regardless of what it points to.

Now consider this version:

class Printer;

class Document {
private:
    Printer printer;
};

Step by step:

  1. class Printer;

    • The compiler knows only that Printer exists.
  2. Printer printer;

    • The compiler must place a full Printer object inside Document.
    • To do that, it must know the size and layout of Printer.
    • Since Printer is incomplete, compilation fails.

So the key question is:

Does the compiler need the full details of the type right now?

  • If no, a forward declaration may be enough.
  • If yes, include the full header.

Real World Use Cases

1. Breaking circular dependencies

Two classes often need to refer to each other.

class B;

class A {
    B* b;
};

Without forward declarations, including both headers into each other can create circular include problems.

2. Reducing compile times

Large codebases may include hundreds of headers. Replacing unnecessary includes with forward declarations can significantly reduce rebuild time.

3. Pimpl pattern

Forward declarations are commonly used in the Pointer to Implementation pattern.

class WidgetImpl;

class Widget {
private:
    WidgetImpl* impl;
};

This hides implementation details and keeps the public header stable.

4. API design with references and pointers

Many library interfaces accept types by reference or pointer in headers while putting implementation code in .cpp files where the full headers are included.

Take Quiz

Real Codebase Usage

In real C++ projects, developers usually follow a simple pattern:

  • Use forward declarations in headers when only pointers or references are needed.
  • Include full headers in .cpp files where member access or object construction happens.

Common patterns

Dependency reduction

// UserService.h
class Database;

class UserService {
public:
    explicit UserService(Database& db);

private:
    Database& db;
};
// UserService.cpp
#include "UserService.h"
#include "Database.h"

Guarding against heavy includes

If a header only needs a declaration for a parameter type, developers avoid including the full header there.

Validation and orchestration classes

Service classes, controllers, managers, and handlers often store references or pointers to other services. This is a good place for forward declarations.

Where full definitions are still needed

  • inline member function definitions inside headers that access members of the other class,
  • templates defined in headers,
  • inheritance hierarchies,
  • by-value members.

If code in the header actually uses the class, not just names it, the full definition is usually required.

Common Mistakes

1. Forward-declaring a type used by value

Broken code:

class Engine;

class Car {
    Engine engine; // Error
};

Why it fails:

  • Engine is incomplete.
  • The compiler cannot determine the size of Car.

How to fix it:

#include "Engine.h"

class Car {
    Engine engine;
};

2. Forward-declaring a base class

Broken code:

class Vehicle;

class Car : public Vehicle { // Error
};

Why it fails:

  • The derived class layout depends on the base class definition.

How to fix it:

#include "Vehicle.h"

class Car : public Vehicle {
};

3. Using members of an incomplete type in a header

Broken code:

class Engine;

class Car {
public:
    void start() {
        engine->ignite(); // Error if Engine is incomplete here
    }

private:
    Engine* engine;
};

Why it fails:

  • Calling ignite() requires the full definition of Engine.

How to fix it:

  • Move the function definition to a .cpp file and include Engine.h there.

4. Assuming references and pointers always solve everything

Pointers and references can be declared with incomplete types, but some related operations still need completeness.

For example, deleting a pointer to an incomplete type in the wrong place can be problematic.

5. Forgetting that inline code in headers is compiled there

Even if a field is only a pointer, any inline method in the header that dereferences it needs the complete type.

class Engine;

class Car {
public:
    int rpm() {
        return engine->getRpm(); // Needs full definition of Engine
    }
private:
    Engine* engine;
};

Comparisons

Usage of another classForward declaration enough?Why
Pointer member (Type*)YesPointer size is known
Reference member (Type&)YesReference can be declared without full layout
Function parameter by pointerYesOnly the type name is needed
Function parameter by referenceYesOnly the type name is needed
Function return type by pointer/referenceYesFull size not needed for declaration
Member object by valueNoCompiler needs full size and layout
Inheritance (class A : public B)NoBase class must be fully defined
Calling member functions in headerNoMembers of the type must be known
Using sizeof(Type)NoRequires complete type
Creating an object (Type x;)NoRequires complete type

Forward declaration vs include

ApproachUse whenTrade-off
Forward declarationYou only need the type nameFewer dependencies, but limited usage
#includeYou need full details of the typeMore coupling, but full access

By value vs by reference

FormNeeds full definition in header?
Type member;Yes
Type* member;No
Type& member;No
void f(Type t);Usually yes for declarations that require complete type use; passing by value means the type itself is copied
void f(Type& t);No
void f(Type* t);No
Take Quiz

Cheat Sheet

Quick rules

  • Use class Name; to forward-declare a class.
  • Forward declaration is enough for:
    • pointers,
    • references,
    • function declarations using pointers/references.
  • Full definition is required for:
    • inheritance,
    • member objects by value,
    • object creation,
    • sizeof,
    • accessing members,
    • inline code that uses the type.

Safe examples

class Foo;

Foo* p;
Foo& r = someFoo;
void useFoo(Foo& f);
void useFooPtr(Foo* f);

Not safe with only a forward declaration

class Foo;

class Bar : public Foo {}; // not enough
Foo value;                 // not enough
sizeof(Foo);               // not enough
value.method();            // not enough

Practical guideline

  • In headers: prefer forward declarations when possible.
  • In source files: include the real header when you need to use the type.

Important edge case

If a destructor, inline function, or template in a header needs the full type, a forward declaration alone is not enough.

FAQ

Can I forward-declare a class used as a pointer member in C++?

Yes. A pointer member such as Type* ptr; only requires the type to be declared, not fully defined.

Can I forward-declare a class used as a reference parameter?

Yes. Function parameters and return types using references or pointers can usually use a forward declaration.

Can I forward-declare a base class?

No. A base class must be fully defined before you derive from it.

Can I forward-declare a class stored as a member object?

No. If a class is stored by value, the compiler must know its full size and layout.

Why do forward declarations help compile time?

They reduce unnecessary header inclusion, which reduces dependency chains and rebuild work.

Why does code sometimes compile in the header but fail when I call a method?

Because declaring a pointer or reference is allowed with an incomplete type, but accessing members of that type requires the full definition.

Should I always prefer forward declarations over includes?

Not always. Use forward declarations when they are sufficient. If the header needs the full type, include the proper header instead.

Take Quiz

Mini Project

Description

Build a small C++ example with two classes, Library and BookManager, to practice when a forward declaration is enough and when a full include is required. The project demonstrates how to keep headers lightweight by using a forward declaration for a dependency stored as a pointer, while including the full definition in the source file where methods are implemented.

Goal

Create two classes that interact through a pointer/reference so you can use a forward declaration correctly in the header and the full definition in the .cpp file.

Requirements

  • Create a Library class with at least one public method.
  • Create a BookManager class that stores a pointer to Library.
  • Use a forward declaration of Library in BookManager.h instead of including Library.h there.
  • Implement BookManager methods in BookManager.cpp and include Library.h in that file.
  • Add a main function that creates the objects and calls the methods.
Take Quiz

Keep learning

Related questions

View more