The common technique relies on the fact that declaring (but not defining) a class is sufficient to create references or pointers to that class. But, to define (implement) a class member function (or call a member function) the class must be defined.
To illustrate, I'll define the two classes task
and task_manager
in separate header files. Each requires a declaration (not a definition) of the other. So the header for task
may be
#ifndef TASK_INCLUDED
#define TASK_INCLUDED
class task_manager; // declare but don't define the task_manager class
class task
{
public:
~task() = default;
void add_child(const task& child); // do not define this function inline in the class definition
private:
friend class task_manager;
task(int id, task_manager& manager) : _id{id}, the_manager{manager} { }
int _id;
task_manager& the_manager;
};
#endif
The constructor of task
can be inline since the_manager
and manager
are both references.
Similarly, task_manager
may be defined in a separate header file, such as
#ifndef TASKMGR_INCLUDED
#define TASKMGR_INCLUDED
class task; // declare but don't define the task class
class task_manager
{
public:
void add_child(const task &, const task &); // do not define this function inline in the class definition
};
#endif
Now, to define the class member functions, both headers need to be included (order of inclusion does not matter) since calling OR defining member functions of any class requires the class definition to be visible to the compiler.
#include "task.h"
#include "taskmgr.h"
void task::add_child(const task &child)
{
the_manager.add_child(*this, child);
}
void task_manager::add_child(task &caller, const task &caller);
{
// whatever
}
If you prefer that the classes are defined in a single header (rather than two, as above) the approach is simple - declare one class, define the other, then define the second. For example;
#ifndef TASK_AND_MGR_INCLUDED
#define TASK_AND_MGR_INCLUDED
class task_manager; // declare but don't define the task_manager class
class task
{
public:
~task() = default;
void add_child(const task& child); // do not define this function inline
private:
friend class task_manager;
task(int id, task_manager& manager) : _id{id}, the_manager{manager} { }
int _id;
task_manager& the_manager;
};
class task; // this declaration optional, since preceding class definition is a declaration
class task_manager
{
public:
void add_child(const task &, const task &);
};
#endif
I reiterate that it is necessary that the task::add_child()
and task_manager::add_child()
member functions not be defined (implemented) within their respective class definitions.