user2218509
user2218509

Reputation: 53

C++ Classes dependent on each other causing a cyclic dependency error

My searches have lead me to believe that the problem I am experiencing is called cyclic redundancy. I don't understand any of the solutions that are posted. I am (fairly) new to C++, coming from a strong Java background.

Basically there are two classes that depend on each other. Class A contains a vector of class B objects, and class B contains methods that require class A objects as input.

Here's code that reproduces the problem.

According to codelite g++, the error is in school.h and is "person was not declared in this scope". It also says "template argument 1 is invalid" and "template argument number 2 is invalid". Then a couple of others, about non-class type "int" on all the vector functions that get invoked.

main.cpp

#include <iostream>
#include <string>
#include "person.h"
#include "school.h"

int main() {
    person p;   
    school s;
    std::cout << p.name << std::endl;
    s.personVect.push_back(p);
    std::cout << s.personVect.size() << std::endl;
    std::cout << s.personVect.at(0).name << std::endl;
    p.test();
    return 0;
}

school.h

#ifndef SCHOOL_H
#define SCHOOL_H
#include <vector>
#include "person.h"

class school
{
public:
    school();
    ~school();

    std::vector<person> personVect;


};

#endif // SCHOOL_H

school.cpp

#include "school.h"    
school::school(){}    
school::~school(){}

person.h

#ifndef PERSON_H
#define PERSON_H
#include <string>
#include <vector>
#include "school.h"


class person {
public:

    std::string name;
    std::string phone;

    school doSomethingWithSchool();

    void test();

    person();
    ~person();

};

#endif // PERSON_H

person.cpp

#include "person.h"
#include <iostream>
using namespace std;

person::person()
{
    name = "marcus";
    phone = "0400000000";
}

person::~person()
{
}

void person::test() {
    cout << this->name;
}
school person::doSomethingWithSchool() {
    school s; 
}

Upvotes: 0

Views: 3272

Answers (4)

azat
azat

Reputation: 3565

Instead of including person #include "person.h"
In header file ("school.h") just write class person; and use pointer to person
And in c++ module ("school.cpp") include "person.h" if you need.

This will also have some benefits in feature. (reduce compilation time)

Useful links:

http://www.codeproject.com/Articles/547275/Whyplusisplusitplusbuildingplussopluslong-3f https://stackoverflow.com/a/553869/328260

Upvotes: 0

maditya
maditya

Reputation: 8896

Your issue is with the fact that school.h has an #include "person.h" statement, and person.h has an #include "school.h" statement.

The solution

In the school class, instead of vector<person>, use vector<person*>. Instead of including person.h here, add the statement class person;. Assuming that your school class is eventually going to modify person objects somehow, put the #include person.h in your school.cpp file, after the statement #include school.h.

The error explained

This is a step-by-step breakdown of what happens when compiling:

  1. In main.cpp, you first include person.h. The preprocessor goes and finds person.h. Since you've never included it before, PERSON_H has not yet been defined and the ifndef PERSON_H is currently true. So we go to the next line in the file.

  2. The first interesting thing that happens in this file is that PERSON_H gets defined (#fdefine PERSON_H). The next time you #include this file, the preprocessor is going to skip the whole file, right to the #endif.

  3. The second interesting thing to happen is that school.h gets included. So the preprocessor finds school.h (we haven't yet processed the School class!), which until now has never been processed.

  4. The first interesting thing in school.h (after the #define SCHOOL_H) is the #include person.h, so the preprocessor returns to person.h (we haven't yet processed the Person class either!). But this time, PERSON_H is already defined, so #ifndef PERSON_H is false, and the preprocessor skips to the #endif in that file (as I mentioned in (2)). So the Person class still hasn't been declared.

  5. We return to school.h, having done nothing. At this point, we are just about to declare/define the school class. The person class has never been declared because our preprocessor jumped from person.h to school.h back to person.h back to school.h, only processing #include directives.

  6. The compiler starts going through the school class definition. The next interesting thing is the line std::vector<person> personVect;. You have tried to instantiate a vector<person>. But person here is unknown (i.e. has not been declared in this scope) because of (5). This explains your two template argument errors: A vector<person> is actually implicitly a vector <person, allocator<person> >. The first template argument is person and the second is allocator<person>. Since person is unknown, both these arguments are invalid.

The solution explained

The fact that school.h includes person.h, and person.h includes school.h, is what causes the cyclic dependency that you alluded to. To avoid this, we need an alternative way to declare that we are going to be using a particular class in a file. The common approach is to use a forward declaration - the statement class person; that I mentioned in the solution, before the definition of school. This is an ordinary declaration that lets the compiler know that person (as used in the school class) refers to a class.

However, in order to construct vector<person>, the compiler also needs to know how much space the class person occupies in memory (i.e. its size), which we aren't providing (and could never manually provide) with the declaration class person;. In fact, the compiler only knows the size of a class once the full definition in the header file has been processed. But if we try to do that, we're back to square one (cyclic dependency, and what not). What we do know is the size of a pointer to person, since that's machine specific (e.g. on a 32-bit machine, the size of a pointer to anything is 32 bits). Hence, we can instantiate a vector<person*> since this is always a vector of 32-bit objects regardless of what person actually is.

Upvotes: 2

Arun
Arun

Reputation: 2092

Try this,

person.h

        #ifndef PERSON_H
        #define PERSON_H
        #include <string>
        #include <vector>

        class school;
        class person {
        public:

            std::string name;
            std::string phone;

            school doSomethingWithSchool();

            void test();

            person();
            ~person();

        };

        #endif // PERSON_H

school.h

      #ifndef SCHOOL_H
      #define SCHOOL_H
      #include <vector>

      class person;
      class school
      {
      public:
          school();
          ~school();

          std::vector<person*> personVect;
      };

      #endif // SCHOOL_H

person.cpp

    #include "person.h"
    #include "school.h"
    #include <iostream>
    using namespace std;

    person::person()
    {
        name = "marcus";
        phone = "0400000000";
    }

    person::~person()
    {
    }

    void person::test() {
        cout << this->name;
    }
    school person::doSomethingWithSchool() {
        school s; 
        return s;
    }

and in main.cpp

    int main()
    {
        person *p = new person;   
        school s;
        std::cout << p->name << std::endl;
        s.personVect.push_back(p);
        std::cout << s.personVect.size() << std::endl;
        std::cout << s.personVect.at(0)->name << std::endl;
        p->test();
        delete p;
        return 0;
    }

Upvotes: 0

Aesthete
Aesthete

Reputation: 18848

The problem can be resolved by designing you class relationships better.

A Person isn't composed of a School, so it doesn't need to have a School member.

A School has a collection of Person objects.

If you want a person to do something with a school, pass it as an argument. This way you can use pointers and forward declarations to solve the issue.

// Person.h
class School; // Forward declare school type.

// Person.cpp
Person::DoSomethingWithSchool(School* school);

Upvotes: 2

Related Questions