Search code examples
c++classpointerscircular-dependency

How can I solve this circular dependency?


I have two classes: DESEngine and UserEvents. DESEngine should be an "owner" of UserEvents, but UserEvents should have access to DESEngines variables and methods by something like:Owner->Method();

But I'm getting multiple errors as per the image: Error List (Errors were included in text form at end of post)

I'm almost certain that almost all the errors are due to circular dependency but I've been unable to solve them :/

DESEngine.h

#ifndef DESENGING
#define DESENGINE

#pragma once
#include <string>
#include <vector>
#include <unordered_map>
#include "boost/any.hpp"
#include "GlobalVariables.h"
#include "TextParser.h"
#include "UserEvents.h"

class GlobalVariables;
class TextParser;
class UserEvents;

class DESEngine
{
public:
// System Classes
    GlobalVariables GVar_User = GlobalVariables();
    GlobalVariables GVar_EventLabels = GlobalVariables();
    UserEvents UsrEvt = UserEvents(*this);

    DESEngine();
    ~DESEngine();

    // Irrelevant stuff omitted

private:
   // Irrelevant stuff omitted
    std::unordered_map<std::string, void(DESEngine::*)(const std::string&)> SystemFunctionPointerMap;
    void DESEngine::ExtractEventParameter(std::string &WordBlock, std::vector<boost::any> &EvtParams);
};

#endif

UserEvents.h

#ifndef USEREVENTS
#define USEREVENTS


#pragma once
#include <string>
#include <vector>
#include <unordered_map>
#include <typeinfo>
#include <boost/any.hpp>
// #include "DESEngine.h" --> moved to DESEngine.cpp

class DESEngine;

class UserEvents
{

public:

    // Class constructor / destructor
    UserEvents(DESEngine &Engine);
    ~UserEvents();

    // Select which function(parameters) to call
    int UserEvents::Choose(const DESEngine::EventWithParams &Event);


private:
    DESEngine Owner;

    // Here we have an unordered map that assigns User Function (pointer) to each Key (string / Alias / Event Name)
    std::unordered_map<std::string, void(UserEvents::*)(const std::vector<boost::any>&)> UserFunctionPointerAliasMap;
// Irrelevant stuff omitted
    };

    #endif

C2079 'UserEvents::Owner' uses undefined class 'DESEngine'  33      
C2027 use of undefined type 'DESEngine' 29      
C4430 missing type specifier - int assumed. Note: C++ does not support default-int  29      
C2143 syntax error: missing ',' before '&'  29      
C2079 'UserEvents::Owner' uses undefined class 'DESEngine'  33      
C2440 '=': cannot convert from 'DESEngine' to 'int' 11      
C2511 'int UserEvents::Choose(const DESEngine::EventWithParams &)': overloaded member function not found in 'UserEvents'    24      
C2671 'UserEvents::Choose': static member functions do not have 'this' pointers 27      
C2228 left of '.at' must have class/struct/union    27      
C2027 use of undefined type 'DESEngine' 29      
C4430 missing type specifier - int assumed. Note: C++ does not support default-int  29      
C2143 syntax error: missing ',' before '&'  29      
C2079 'UserEvents::Owner' uses undefined class 'DESEngine'  33      
C2027 use of undefined type 'DESEngine' 29      
C4430 missing type specifier - int assumed. Note: C++ does not support default-int  29      
C2143 syntax error: missing ',' before '&'  29  

Solution

  • Since this is a has a relationship between DESEngine and UserEvents with in its class structure you can slightly redesign your classes that shouldn't lose any functionality. This slight change in design should improve on memory management and the relationships or bonds between classes. When a class Has a <T> or Owns an object to another class or structure the owning class only needs to include the objects header in its own header and shouldn't need a class prototype declaration. Now the class that belongs to another should not include the owning class's header within its own header but does need the class prototype declaration and needs to include the owning class'sheaderin itsimplementation` file.

    Also as for the ownership relationship that is where smart pointers come into play. Since your DESEngine class object owns a UserEvents object it is safe to have a std::unique_ptr<T> in the owning class. This will create a pointer of T for you and should also help to manage that allocation and de-allocation of memory and resetting the pointers to avoid both memory leaks and dangling pointer issues. With this only this instance of DESEngine will be able to access and modify that instance or copy of UserEvents. Now if you need other resources to modify and have access to UserEvents besides the engine you can easily change that to a std::shared_ptr<T>.

    Here is an example of a set of classes...

    EXAMPLE

    Owner.h

    #ifndef OWNER_H
    #define OWNER_H
    
    #include "SomeObject.h" // This class is owned by Owner since Owner has a SomeObject class object
    
    /*No Need To Have Class Prototype Declaration*/ // #include "SomeClass.h"
    // This does however need to be included in "Owner.cpp" 
    
    class Owner {
        std::unique_ptr<SomeObject> myRestrictedObject;
        std::shared_ptr<SomeObject> mySharedResourceObject;
    };
    #endif // OWNER_H 
    

    Owner.cpp

    #include "stdafx.h" // if used  
    #include "SomeObject.h"
    #include "SomeOtherClasses.h"
    
    // Class Body Or Implementation Definitions
    

    SomeObject.h

    #ifndef SOME_OBJECT_H
    #define SOME_OBJECT_H
    
    // Since Owner.h already has #include `THIS.h` we do not want to include it here but we will need this:
    class Owner; 
    
    class SomeObject {
    private:
         // Google Search For Using Friend relationship.
    };
    
    #endif // SOME_OBJECT_H
    

    SomeObject.cpp

    // We need to include Owner.h here
    #include "Owner.h"
    

    Now when working with SomeObject that is owned by another, you should be able to alleviate the need to contain a pointer or a reference to the owning object simply by making these two classes friends with each other either full friend class or by specific methods within each class. With this you can control the internal relationship of how the two class interact with other. With careful design the user of your product code shouldn't have to worry about the internal implementation details of your functions, class's and their methods, and templates if it is well structured and the functions do what they are meant to do and are named accordingly for readability.

    Now if you want this object to contain a pointer of its owning object then you might want to step away from using friends but also try avoiding the need for either of them to accept a pointer or an object of each other. Another words both classes should have default constructors this way if one relies on another for construction it can always use the default constructor first then you can use an initialize or an update method to change the class's internal variable pointer to another class by that method. In some cases or needs you could have one class be a static class as opposed to an actual object. Then you would need a static get function to return its *this back to you as a static pointer then other depending classes could use the static pointer as well especially if you will only ever have 1 instance of that class object.

    For using friend that can easily be googled to see how it is done. As for the later option I can give a small illustration if requested.

    Since it appears that your application should only ever have a single instance of either DESEngine and UserEvents for there should be no need to have multiple instances of these unless if you are pooling a bunch of UserEvents objects then you could derive these two class from a base Singleton interface. Then make sure both of these objects declares a static pointer to itself as well a get method for that pointer. Then other classes that use this object can use that static pointer, or similarly but instead of being a static singleton you can use smart pointers or even a combination of both. It all depends on your particular needs.

    The placement of your includes and your class prototype declarations are the most important deciding factor when it comes to circular dependencies. Now you are also showing other classes that these two classes use with other header files being included and it could be possible that one of those might be the culprit too but didn't show up until just now with these two classes.

    I would also like to add on to WhozCraig's comment that you are passing a reference of DESEngine to a UserEvents constructor. So let's see what the compiler is trying to understand from you...

    Somewhere in your source in some other function you are creating an instance of DESEngine such as;

    main.cpp

    #include "DESEngine.h"
    
    int main() {
        DESEngine engine; // This is okay because you declared it with a default constructor.
    
    }
    

    So the compiler goes into DESEngine.h and looks at the declaration of this class before the definitions and it says to itself okay I need a UserEvents class let's go to its constructor, Okay this construct requires a Reference to a DESEngine object and that is coming from DESEngine variable declaration to the current UserEvents and I haven't yet finished declaring or defining the DESEngine object. So it goes around the circle trying to complete the definitions based on the declarations.