2

first of all, I want to say that I am aware that this kind of question has been asked before (e.g. here Resolving a Circular Dependency between Template Classes).

However, this solution (Separating the declaration from the implementation) only works when putting both classes into one file. In my case, I have a StateManager and a State class both of which are fairly large and guaranteed to grow. Thus having them in one big file seems unsatisfactory to mine.

Here some important code snippets:

// Forward declare the StateManager --> does not work (incomplete type)
class State
{
public:
    template <class TData>
    void RequestStackPush(ID stateId, std::shared_ptr<TData> data);
private:
    StateManager & stataManager;
}

Here the implementation of the RequestStackPush() method

template<class TData>
    inline void State::RequestStackPush(ID stateId, std::shared_ptr<TData> data)
    {
        // Uses the state manager's PushState() method - here the issue with the incomplete type arises
        stateManager.PushState<TData>(stateId, data);
    }

Obviously, the StateManager uses the State class all the time. It creates it calls methods etc. so forward declaration is no solution here. Just to give You an example:

template<class TData>
    inline void StateManager::PushState(State::ID stateId, std::shared_ptr<TData> data)
    {
        std::unique_ptr<BasePendingChange> pendingChange = std::make_unique<PendingPushDataChange<TData>>(Push, stateId, data);
        pendingChangeQueue.push(std::move(pendingChange));
    }

Currently, both classes are in one big file. First, the declaration of the State class with the StateManager being forward declared followed by the declaration of the StateManager class followed by the implementation of the above described State::RequestStackPush() method and finally the implementation of all the StateManager template methods.

How can I separate this into two different files?

Zub
  • 808
  • 3
  • 12
  • 23
  • Have you considered simply removing the `StateManager` reference and `RequestStackPush` method? How bad would that be for the rest of your code? – Sebastian Redl Dec 14 '17 at 09:59
  • Bad since its crucial for being able to pass any data to a derived State. The Request State Push is needed in order to delay the creation of the pushed state. Creating a state while updating or rendereing the state stack may lead to errors – Adrian Albert Koch Dec 14 '17 at 10:02
  • I meant, why can't people use StateManager::PushState directly? – Sebastian Redl Dec 14 '17 at 10:06
  • @SebastianRedl Since, as I said, this may or actually will interrupt the update loop in which the state stack is looped – Adrian Albert Koch Dec 14 '17 at 10:08

1 Answers1

2

However, this solution (Separating the declaration from the implementation) only works when putting both classes into one file.

No, it doesn't only work in one file. You can always construct an identical file by including sub-headers. It just requires you to do something that would be unusual with non-templates (although same technique works with all inline function definitions): You need to include a file after defining the class. Headers are not limited to being at the top of the file despite the name given to them.

So, in one file:

  • Declare StateManager
  • Define State
  • Include definition of StateManager
  • Define member functions that depend on definition of StateManager

Nothing unusual in the other file:

  • Include definition of State
  • Define StateManager and its member functions.

The end result is that including either header produces the same definitions and declarations in the required order. So, this splitting of files does in no way help with limiting the amount of re-compilation caused by modifying one of the headers.

It may be a matter of taste, but I always include definitions required by inline functions (including members of templates and template functions) after the definition of the class. That way I don't need to worry whether doing so is necessary.

eerorika
  • 232,697
  • 12
  • 197
  • 326
  • He needs the full definition of `State` before the definition of `StateManager` since some function signatures reference inner types. But the general idea is right. – Sebastian Redl Dec 14 '17 at 10:05
  • @SebastianRedl ah, I didn't notice the dependency was in argument. Fixing... Fixed. – eerorika Dec 14 '17 at 10:06
  • @AdrianKoch `State.h` must not include `StateManager.h` before `State` has been defined. Otherwise there is a circular dependency. You must get rid of that include. It's not possible to say how, without a [mcve]. – eerorika Dec 14 '17 at 10:20
  • The other trick you might employ in each child header is to assert that the parent header has been loaded, so your ordering has been maintained. The standard headers is full of examples like: #if !defined _MATH_H && !defined _COMPLEX_H # error "Never use directly; include instead" #endif – Gem Taylor Dec 14 '17 at 10:28