Student and class does not name a type

Pages: 12
Hey guys,

I'm making a simple school management system, that keeps track of students grades attendance, allows teachers to add students to classes etc etc

but right from the get-go I have run into a problem, I have already declared 3 classes in separate headers Class.h Subject.h and Student.h

I am getting an error that Student and Class does not name a type but both header are rightfully included.

also I decided to put the header guards before the #include directives but that didn't solve the problem and instead my compiler gave me an error saying that include nested too deeply,

I wonder what I am doing wrong?

thanks


Student.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

#ifndef STUDENT_H
#define STUDENT_H
#include <string>
#include "Class.h"

class Student
{
    public:
        Student();
        std::string getName();
        std::string getDOB();


        virtual ~Student();

    protected:

    private:
        std::string name;
        std::string dateOfBirth;
        Class* grades;
};

#endif // STUDENT_H



Class.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

#ifndef CLASS_H
#define CLASS_H
#include "Student.h"

class Class
{
    public:
        Class();
        std::string getClassName();
        Student* getStudents();
        virtual ~Class();

    protected:

    private:
        std::string className;
        Student* students;
};

#endif // CLASS_H
  


Subject.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

#include <string>

#ifndef SUBJECT_H
#define SUBJECT_H


class Subject
{
    public:
        Subject();
        std::string getSubjectName();
        char getGrade();

        virtual ~Subject();

    protected:

    private:
        std::string subjectName;
        char grade;
};

#endif // SUBJECT_H
both header are rightfully included.
Nope. You have a circular dependency: Student.h includes Class.h includes Student.h etc.

In Student.h you may use a forward declaration, since Class is used as a pointer:
1
2
3
4
5
6
7
#include "Class.h"

class Class; // forward declaration
class Student
{
...
};
Just wanted to add: In both Student.h and Class.h, only a forward declaration is needed, because only pointers are declared.

In the corresponding .cpp files is where you can #include the full definition, if you need to dereference those pointers. This will break the circular dependency.
thanks guys that worked :)

I thought the header guards would prevent the circular dependency from even happening in the first place for example student.h includes class.h which again in turn includes student.h but shouldn't the header guard in student.h prevent this from happening?

also lets say Class wasn't a pointer could I still use a forward declaration to solve this issue?

thanks
Pretend you're the preprocessor/compiler. Let's say you're trying to compile foo.cpp, and foo.cpp #includes Student.h.

You enter Student.h, set the include guard, and then encounter #include "Class.h". You then enter Class.h, set its include guard, and then encounter #include "Student.h" again. You enter Student.h, but this time immediately jump out of it because of the include guard. So you continue on to the definition of the Class class itself. Remember, at this point, you still have not encountered the definition of a Student yet. The compiler doesn't know what a Student is. It sees that Class has a Student*, but it doesn't know what a Student* is, so it fails, saying Student does not name a type.

The include guards break the infinite recursion, but can't help provide information that doesn't exist. If you put the #includes before any header guards, this is what would cause the "nested too deeply" error due to the infinite recursion.

also lets say Class wasn't a pointer could I still use a forward declaration to solve this issue?
If a class contains a Class object as a member, it needs to know what the sizeof(Class) is. It can only do this if it has the full definition of Class.
Last edited on
Oh ok so my main problem was that I haven't actually defined or given a class body to Student, if I was to give a class body would the compiler not complain anymore?

and Oh I see,as far as putting the #includes before the header guards, it will never reach the header guards as each class will keep including one another.

thanks
I defined Class in a separate cpp file in the same project but the compiler is still complaining about class does not name a type :/

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include "Class.h"
#include "Student.h"

Class::Class()
{
    //ctor
}

Class::Class(std::string className,Student* students): className(className),students(students){}

std::string Class::getClassName(){
     return className;
}

Student* Class::getStudents(){
    return students;
}

Class::~Class()
{
    delete students;
}

Last edited on
I need more context. What do your header files now look like?

SchoolClass.h
1
2
3
4
5
6
7
8
9
10
11
#ifndef SCHOOL_CLASS_H
#define SCHOOL_CLASS_H

class Student;

class SchoolClass {
  public:
    Student* student;  
};

#endif 


Student.h
1
2
3
4
5
6
7
8
9
10
11
#ifndef STUDENT_H
#define STUDENT_H

class SchoolClass;

class Student {
  public:
    SchoolClass* school_class;
};

#endif 


main.cpp
1
2
3
4
5
6
7
8
9
10
11
12

#include "Student.h"
#include "SchoolClass.h"

int main()
{
    SchoolClass school_class;
    Student student;
    
    student.school_class = &school_class;
    school_class.student = &student;
}
Last edited on
I've changed them a little bit replaced the pointers with vectors as I feel they are more appropriate

but now I can't use a vector because the compiler is giving me the following error -
C:\Program Files MinGW\lib\gcc\mingw32\5.1.0\include\c++\bits\stl_vector.h|161|error: invalid use of incomplete type 'class Class'|


Class.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

#ifndef CLASS_H
#define CLASS_H

#include "Student.h"

class Class
{
    public:
        Class();
        Class(std::string className,Student* students);
        Class(const Class& other);
        Class& operator=(const Class& other);
        std::string getClassName();
        Student* getStudents();
        virtual ~Class();

    protected:

    private:
        std::string className;
        Student* students;
};

#endif // CLASS_H 


Class.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39

#include "Class.h"
#include "Student.h"

Class::Class()
{
    //ctor
}

Class::Class(std::string className,Student* students): className(className),students(students){}

Class::Class(const Class& other){

     this->className = other.className;
     this->students = NULL; // temp
    // this->students = new students[sizeof(other.students)/sizeof(students)];
}

Class& Class::operator=(const Class& other){

     this->className = other.className;
     this->students = NULL; // temp
     return *this;

}

std::string Class::getClassName(){
     return className;
}

Student* Class::getStudents(){
    return students;
}

Class::~Class()
{
    delete students;
}


Student.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

#ifndef STUDENT_H
#define STUDENT_H
#include <string>
#include <vector>
//#include <Class.h>

class Class;

class Student
{
    public:
        Student();
        Student(std::string name,std::string dateOfBirth);
        std::string getName();
        std::string getDOB();
        std::vector<Class> getGrades();

        virtual ~Student();

    protected:

    private:
        std::string name;
        std::string dateOfBirth;
        std::vector<Class> grades;
};

#endif // STUDENT_H



Student.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

#include "Student.h"

Student::Student()
{
    //ctor
}

Student::~Student()
{
    //dtor
}


Last edited on
In your student class what is the purpose of that std::vector<Class>?

IMO, your student should not really have a vector<Class> (especially named grades) since the Class class doesn't really hold much data that would be needed by the Student. IMO, a vector<Grades> would probably be a better choice (Grades could be a plain int or perhaps another user defined type).

But as designed your two classes have circular dependencies, which must be resolved (removed) before proceeding.

By the way you probably shouldn't be using a "virtual" destructor unless you plan on using that class as a base class.

The vector of classes will hold each class and their corresponding grade in that class.

is the circular dependencies causing the C:\Program Files "\MinGW\lib\gcc\mingw32\5.1.0\include\c++\bits\stl_vector.h|161|error: invalid use of incomplete type 'class Class'| " error?

as mentioned by ganado and coder777 I could forward declare class if it was a pointer but now I am using a vector of "Classes". but not too sure how I would fix the circular dependency without forward declarations?

I think I know the reason why I am getting that error BUT can't seem to get around it because if I #include "Class.h" in student.h that would fix the problem since the class is declared there but if I do add #include "Class.h" it will cause a circular dependency :/ ?

on second thoughts to the above paragraph my thinking "if I #include "Class.h" in student.h that would fix the problem since the class is declared there" must be wrong as even when I #include "Class.h" I get the error - include\Student.h|16|error: 'Class' does not name a type| so my assessment was wrong there?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

#ifndef STUDENT_H
#define STUDENT_H
#include <string>
#include <vector>
#include "Class.h"

//class Class;

class Student
{
    public:
        Student();
        Student(std::string name,std::string dateOfBirth);
        std::string getName();
        std::string getDOB();
        Class getGrades();
       // std::vector<Class> getGrades();

        ~Student();

    protected:

    private:
        std::string name;
        std::string dateOfBirth;
        Class grades;
        //std::vector<Class> grades;
};

#endif // STUDENT_H



very good point I don't plan on extending the classes so I can get rid of virtual, codeblocks like to automatically add them to the class skeleton.

thanks
Last edited on
Hey guys, I still have a few questions

In the corresponding .cpp files is where you can #include the full definition, if you need to dereference those pointers. This will break the circular dependency.


I tried this but firstly to no avail I got that student and class where not declared as a type, but when I used a forward declaration of both classes in the header file it works fine.

Class.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

//#include "Student.h"
#include <vector>
#include <string>

class Student; // if I don't forward declare class student I will get an error Student does not name a type, if I keep it everything works fine

class Class
{
    public:
        Class();
        Class(std::string className,std::vector<Student> students);
        Class(const Class& other);
        Class& operator=(const Class& other);
        std::string getClassName();
        std::vector<Student> getStudents();
        virtual ~Class();

    protected:

    private:
        std::string className;
        std::vector<Student> students;
};

#endif // CLASS_H


Class.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

#include "Class.h" // included in the cpp source file instead of header 
#include "Student.h" // ^^

Class::Class()
{
    //ctor
}

Class::Class(std::string className,std::vector<Student> students): className(className),students(students){}

Class::Class(const Class& other){

     this->className = other.className;
     this->students = other.students; // temp
    // this->students = new students[sizeof(other.students)/sizeof(students)];
}

Class& Class::operator=(const Class& other){

     this->className = other.className;
     this->students = other.students; // temp
     return *this;

}

std::string Class::getClassName(){
     return className;
}

std::vector<Student> Class::getStudents(){
    return students;
}

Class::~Class()
{
    //delete students;
}


Student.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

#ifndef STUDENT_H
#define STUDENT_H
#include <string>
#include <vector>
//#include "Class.h"

class Class; // if I don't forward declare class student I will get an error Student does not name a type, if I keep it everything works fine

class Student
{
    public:
        Student();
        Student(std::string name,std::string dateOfBirth);
        std::string getName();
        std::string getDOB();
       // Class* getGrades();
        std::vector<Class> getGrades();

        ~Student();

    protected:

    private:
        std::string name;
        std::string dateOfBirth;
        std::vector<Class> grades;
};

#endif // STUDENT_H



Student.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

#include "Student.h"
#include "Class.h"

Student::Student()
{
    //ctor
}

Student::Student(std::string name,std::string dateOfBirth):name(name),dateOfBirth(dateOfBirth){


}

Student::~Student()
{
    //dtor
}

std::string Student::getName(){
    return name;
}


So my questions,

A)how come not only do I have to #include the headers in both source cpp files but also forward declare the classes in the header? obviously Class.h needs to know Student exists so I'm guessing that's why we forward declare it BUT wouldn't the linker take care of this without a forward declaration? shouldn't the linker look at Class.h and see that Student is not declared in that header so it should check another header or cpp file for the declaration?

B) Let's say I decided not to do the above step and kept the code the way it was ( still have the circular dependency), how come when we use a pointer it works fine? and how come when using a pointer it breaks the circular dependency?

C) similar to question B, why is the circular dependencies stopping
me from using objects on the stack instead of pointers?

D) I think I read something about the compiler knows that each pointer to an object is the same size therefore we don't need to declare Student and therefore we don't need to know the actual size of the object in this case Student? BUT how does the compiler know how much memory to allocate when creating a Student class on the heap( Student* stu = new Student; )


*edit also looks like I spoke too soon, the forward declaration didn't fix the issue, seems like it compiles fine but when I declare a Student object on the stack it fails and also when I try to add a student object to a vector again it fails

 C++\SchoolManager\main.cpp|15|error: variable 'Student stu' has initializer but incomplete type| 


1
2
3
4
5
6
7
8
9
10
11
12
13
14

#include <iostream>
#include "Class.h"

using namespace std;

int main()
{

      Student stu("Adam","21/07/1993");
      vector<Student> students;
}



how come this is happening?


Thanks :)
Last edited on
A) What you mean is certainly the compiler. And no the compiler doesn't guess which type it should use. You need to tell.
The linker puts all the code together from the generated object files and libraries.

B) It is not he pointer that makes it compile. It is omit including the header.

C) stack is the wrong word here. The point is whether you can use an complete object or not.
You cannot use the complete object when the size is unknown such as in forward references.

D) Yes. The compiler knows the pointer size as much as all other primitives such as int etc.
BUT how does the compiler know how much memory to allocate when creating a Student class on the heap( Student* stu = new Student; )
Well, in that case you need the full definition of the class (-> #include)


how come this is happening?
The compiler doesn't know definition of Student because you did not include it directly or indirectly.
Thanks coder777 much appreciated but my dumb brain is still quite confused lol

I'm still not quite sure why forward declaring the class "Class" in Student will allow me to use Student as a pointer but won't allow me to use it as a normal object, let me walk through the steps as far as I know it.

I'll walk you through what I think is happening,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

// main.cpp

#include <iostream>
#include "Class.h"

using namespace std;

int main()
{
    Student stu("adam","17/07/1993");
    //Class cl("History",stu);

    return 0;
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

// class.h
#ifndef CLASS_H
#define CLASS_H

#include <string>
#include "Student.h"

class Class
{
    public:
        Class();
        Class(std::string className,Student students);
        Class(const Class& other);
        Class& operator=(const Class& other);
        std::string getClassName();
        Student getStudents();
        ~Class();

    private:
        std::string className;
        Student students;
};

#endif // CLASS_H


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// class.cpp

#include "Class.h" // included in the cpp source file instead of header


Class::Class()
{
    //ctor
}

Class::Class(std::string className,Student students): className(className),students(students){}

Class::Class(const Class& other){

     this->className = other.className;
     this->students = other.students; // temp
    // this->students = new students[sizeof(other.students)/sizeof(students)];
}

Class& Class::operator=(const Class& other){

     this->className = other.className;
     this->students = other.students; // temp
     return *this;

}

std::string Class::getClassName(){
     return className;
}

Student Class::getStudents(){
    return students;
}

Class::~Class()
{
    //delete students;
}



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
// Student.h

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

class Class;

class Student
{
    public:
        Student();
        Student(std::string name,std::string dateOfBirth);
        std::string getName();
        std::string getDOB();
        Class* getGrades();
        ~Student();

    private:
        std::string name;
        std::string dateOfBirth;
        Class* grades;
};

#endif // STUDENT_H



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

// Student.cpp

#include "Student.h"

// Student.cpp

#include "Student.h"

Student::Student()
{
    //ctor
}

Student::Student(std::string name,std::string dateOfBirth)
:name(name), dateOfBirth(dateOfBirth)
{


}

std::string Student::getName(){
     return name;
}

std::string Student::getDOB(){
   return dateOfBirth;
}


Student::~Student()
{
    //dtor
}


Using the above classes as reference,

So first we enter main, main includes <iostream> so the contents of iostreams header will essentially be copy and pasted into main.cpp, next the compiler includes or should I say the pre processor(?) pastes the contents of Class.h into Main.cpp, so we enter Class.h

We hit a header guard, the header guard is now set so we don't include the same header twice, we then include string( not important so we glance over it ) next we include Student.h so we enter "Student.h".

Student.h we hit a header guard so we set Student.h's header guard and move onto including string next we forward declare the class "Class", then we define class Student, we are able to use Class as a pointer because we don't need to know the size of Student( how come? still not too sure why) we then hit the endif and exit Student.h

now we are back in Class.h , we then define the class Student but we can use Student as a pointer and not as a pointer right? since we copied the contents of Student into Class.h which in turn includes the definition of Student.h.

we then hit the endif of Class.h, we are now back in Main.cpp?


thanks :)
Last edited on
because we don't need to know the size of Student( how come? still not too sure why)

Because at that point in time, it only needs to know that it's a pointer, and the compiler knows what a size of a pointer is.
(If the compiler needed to know the full definition of a Student to know what a Class is, and needed to know the full definition of Class to know what a Student is, this is the origin of the circular dependency)

Your summary sounds accurate.

BTW, if you are using a Student object in main, you ought to #include "Student.h" directly, and not rely on Class.h to implicitly include it.
Last edited on
Thanks Ganado that makes sense,how would I go about breaking the circular dependency ?, with a forward declaration as you mentioned this will only allow me to use a pointer.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
main.cpp

#include <iostream>
#include "Class.h"
#include "Student.h"

using namespace std;

int main()
{
    //Student stu("adam","17/07/1993");
    //Class cl("History",stu);


    return 0;
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
Class.h

// class.h
#ifndef CLASS_H
#define CLASS_H

#include <string>
//#include "Student.h" // removed
class Student;

class Class
{
    public:
        Class();
        Class(std::string className,Student students);
        Class(const Class& other);
        Class& operator=(const Class& other);
        std::string getClassName();
        Student getStudents();
        virtual ~Class();

    private:
        std::string className;
        Student students;
};

#endif // CLASS_H



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40

// class.cpp

#include "Class.h" // included in the cpp source file instead of header


Class::Class()
{
    //ctor
}

Class::Class(std::string className,Student students): className(className),students(students){}

Class::Class(const Class& other){

     this->className = other.className;
     this->students = other.students; // temp
    // this->students = new students[sizeof(other.students)/sizeof(students)];
}

Class& Class::operator=(const Class& other){

     this->className = other.className;
     this->students = other.students; // temp
     return *this;

}

std::string Class::getClassName(){
     return className;
}

Student Class::getStudents(){
    return students;
}

Class::~Class()
{
    //delete students;
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

// student.h

#ifndef STUDENT_H
#define STUDENT_H
#include <string>
//#include "Class.h"  // removed

//class Class;

class Student
{
    public:
        Student();
        Student(std::string name,std::string dateOfBirth);
        std::string getName();
        std::string getDOB();
        Class getGrades();
        ~Student();

    private:
        std::string name;
        std::string dateOfBirth;
        Class grades;
};



1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29



Student::Student()
{
    //ctor
}

Student::Student(std::string name,std::string dateOfBirth)
:name(name), dateOfBirth(dateOfBirth)
{


}

std::string Student::getName(){
     return name;
}

std::string Student::getDOB(){
   return dateOfBirth;
}


Student::~Student()
{
    //dtor
}


I moved the #includes to the main cpp file breaking the circular dependency but it still tells me -\C++\SchoolSystemProblem\src\Class.cpp|11|error: 'students' has incomplete type
|

from what I researched is there is no way of breaking a circular dependency like the one I have here right? the only things can you can do is use forward declarations like what we did previously to fix the problem or just use better class design,

is there a way to fix the circular dependency in my case without using forward declarations?

Last edited on
For your latest code, it might help if, instead of thinking of it as a "circular dependency", think of it as just "physically impossible".

Imagine if a Duck object contained a Goose object, and a Goose object contained a Duck object. What's inside a duck? A goose. What's inside that goose? A duck. Infinitely. It doesn't make physical sense.

The same thing is happening here. You have a Student containing a Class, but a Class contains a Student. It's not possible logically, regardless of what the C++ compiler says.

I might have more to say later but just wanted to say that before I left.

__________________________________________________________________

I'm going to assume the following setup:
A Classroom has a list of Students. [I'm using the term 'Classroom' just because it's confusing to talk about school classes vs C++ classes]
Each Student also has a pointer to the current class they belong to.
This is something how that would look like:

main.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
#include <vector>
#include "Classroom.h"
#include "Student.h"

using namespace std;

int main()
{
	
    std::vector<Student> students {
		{ "Alice",   "11/21/2019" },
		{ "Bob",     "01/02/1993" },
		{ "Charlie", "07/20/1969" }
	};
	
    Classroom classroom("History", students);

	std::vector<Student>& students_in_classroom = classroom.getStudents();
	Student& alice = students_in_classroom[0];
	std::cout << alice.getClassroomName() << '\n';

    return 0;
}


Classroom.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// Classroom.h
#ifndef CLASSROOM_H
#define CLASSROOM_H

#include <string>
#include <vector>

class Student;

class Classroom
{
    public:
        Classroom(std::string className, const std::vector<Student> students);
        std::string getName();
        std::vector<Student>& getStudents();

    private:
        std::string name;
        std::vector<Student> students;
};

#endif // CLASSROOM_H 


Classroom.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include "Classroom.h"
#include "Student.h" // to be able to access members of student

Classroom::Classroom(std::string className, const std::vector<Student> students)
: name(className), students(students) {
	
	// point each student back to the classroom
	for (Student& student : this->students)
	{
		student.setClassroom(this);
	}
}

std::string Classroom::getName() {
     return name;
}

std::vector<Student>& Classroom::getStudents() {
    return students;
}


Student.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#ifndef STUDENT_H
#define STUDENT_H
#include <string>

class Classroom;

class Student
{
    public:
        Student(std::string name, std::string dateOfBirth);
        
        std::string getName();
        std::string getDOB();
        Classroom* getClassroom();
        std::string getClassroomName();
		
        void setClassroom(Classroom* classroom);

    private:
        std::string name;
        std::string dateOfBirth;
        Classroom* classroom;
};

#endif 


Student.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include "Student.h"
#include "Classroom.h"

Student::Student(std::string name,std::string dateOfBirth)
 : name(name), dateOfBirth(dateOfBirth), classroom(nullptr) { }

std::string Student::getName() {
    return name;
}

std::string Student::getDOB() {
    return dateOfBirth;
}

Classroom* Student::getClassroom() {
    return classroom;
};

void Student::setClassroom(Classroom* classroom)
{
    this->classroom = classroom;
}

// Just an example to show the need to dereference the Classroom pointer
std::string Student::getClassroomName()
{
    if (classroom != nullptr)
        return classroom->getName();
    else
        return "<none>";
}


g++ classroom.cpp student.cpp main.cpp -o main

Output:
History


This breaks any would-be circular dependency.
A classroom contains a list of Students, but Students only have a pointer back to which classroom they belong to.

When declaring a pointer or a container of objects, you don't need to know the member variables/functions of a class, so you can get away with a forward declaration.
But to actually use the member variable, e.g. (my_student.my_object++;) then you need to have the full class definition known.
Last edited on
So circular dependencies doesn't always involve header files? a circular dependency is one class being a member of another class and that other class being a member of the first class, so A is a member of B and B is a member of A, the problem is one has to be declared first right? this is the problem of circular dependency?

I have a few questions on that footnote, so the main one is about the compiler, how does the compiler work in this sense, why do we need to forward declare classes? I thought compilation creates object code but not one line at a time like an interpreter, so shouldn't the compiler not be able to realise that class B was declared albeit after class A?


As you shown in the example you can use a container of Students but how is this possible? wouldn't the compiler still need to know the size of each Student so the vector in this case can allocate enough memory? does it have to do with a vector dynamically allocating memory?? and if we where to call a member function in the class it would fail right??
in other words if I make no sense I'll revert to why can you use Student objects in a container?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
using namespace std;

class B;

class A{

   public:
     B* b; // legal kind of understandable
     vector<B> bs; // but how is this legal? we don't know the size of B right?

};

class B{


  public:
      A a;
};



Thanks Ganado much appreciate you breaking down this problem for me :)
Line 9 technically has undefined behavior before C++17, although mainstream implementations supported this for std::vector, std::list, and std::forward_list via library extension.

As of C++17, std::vector, std::list, and std::forward_list are written specifically to work with incomplete types. It remains undefined behavior to instantiate any other container with an incomplete type.

This works at all because the member functions of a class template are not instantiated (i.e., written out by the compiler) until those member functions are used in the program. While most member functions require that B is complete, the class body alone does not (assuming the allocator type meets some basic requirements).

As such, you can't do very much with your vector<B>, unless you wait until B is completed.
Last edited on
why can you use Student objects in a container?
Well, a container like vector is a class itself. Why shouldn't it use a pointer as well?

wouldn't the compiler still need to know the size of each Student so the vector in this case can allocate enough memory?
The sheer existence of the object doesn't mean that it allocates any memory.

As mbozzi stated: You need the full definition as soon as you are trying to do something with the object.
Pages: 12