0

I am writing a task system. Tasks are created/managed through a task_manager class. A task is basically a task_id and a task_manager&. I then want to have a method like e.g. add_child(const task& child) on my task class that then calls task_manager.add_child(*this, child) from the task_manager.

class task {
  public:
    ~task() = default;

    void add_child(const task& child) {
      _task_manager.add_child(*this, child);
    }

  private:
    friend class task_manager;

    task(int id, task_manager& manager) : _id{id}, _manager{manager} { }

    int _id;
    task_manager& _manager;
};

My problem now is, that i do not come around a circular dependency. The task_manager needs the task because it stores them in e.g. a std::vector<task> but the task also needs the task_manager in order to call the methods.

Are there any patterns i am not aware of that help me with this kind of interface? Are there any similar alternatives?

Symlink
  • 383
  • 2
  • 12
  • 3
    split the code into header and source and it should be fine. `task_manager& _manager;` is fine with a forward declaration – 463035818_is_not_an_ai Jan 19 '22 at 11:46
  • definition of method (which requires the other class) should be (in cpp) after both class definitions. – Jarod42 Jan 19 '22 at 11:46
  • Does this answer your quesiton? https://stackoverflow.com/questions/4757565/what-are-forward-declarations-in-c – 463035818_is_not_an_ai Jan 19 '22 at 11:47
  • afaik `friend class task_manager;` is sufficient as declaration. The link isnt a real duplicate, but rather some information on the "pattern" you are looking for – 463035818_is_not_an_ai Jan 19 '22 at 11:47
  • Define `task::add_child()` and the constructor of `task` after the definition of `task` (i.e. not inline in the class). Then the definition of the class `task` only needs to see a declaration (not a definition) of `task_manager`. Both `task_manager` and `task` will need to be defined (not just declared) before any code that calls their member functions. For example, the definition of `task` and definition of `task_manager` must BOTH be visible to the compiler (declarations are not enough) before defining `task::add_child()`. – Peter Jan 19 '22 at 11:52

1 Answers1

1

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.

Peter
  • 35,646
  • 4
  • 32
  • 74
  • Thank you for the great example. One more question: how would i do this then task has a templated function that cant be placed in a source file? – Symlink Jan 19 '22 at 14:30
  • Define the templated functions in the header after the classes are defined. (Technically you can define any member function inline in the header, but - in your situation with classes that use references/pointers to each other - those inline definitions need to be after the class definition, not within it). – Peter Jan 19 '22 at 14:47