mbozzi wrote: |
---|
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. |
Oh, I didn't actually know that wasn't necessarily guaranteed to work before C++17. I suppose it's because particular libraries could do small-size optimization or something of that nature.
mbozzi wrote: |
---|
t remains undefined behavior to instantiate any other container with an incomplete type. |
Undefined behavior, though? How could it even compile if there isn't a complete type when one is required?
adam2016 wrote: |
---|
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? |
C++ has separate compilation. The question the compiler needs to ask is "can I create an object file from this source file [technically, translation unit]". So Student will become its own object file when you compile student.cpp, Classroom will become its own object file when you compile classroom.cpp, main will become its own object file when you compile main.cpp. [Note: Don't confusing "object file" with the OOP notion of an "object" -- two separate things, unfortunate names]
If you're only declaring a pointer to a class, the compiler doesn't need to know the full definition to be able to compile that file. If you are declaring an object of that class, or using a member of that class, then the compiler needs to know the definition of the class. Otherwise, it just needs to know that it
is a class.
adam2016 wrote: |
---|
why can you use Student objects in a container? |
You can't
use Student objects in a container if Student hasn't been defined yet. You can still
declare the vector though, because a vector, under the hood, will just look (something like) this:
1 2 3 4 5 6
|
class vector_of_some_type {
some_type* pointer_to_dynamic_array;
size_t size;
size_t capacity;
// etc.
};
|
(As mbozzi said, this is only guaranteed for std::vector after C++17, which I just learned)
___________________________________
In other words:
1 2 3
|
Foo foo; // requires Foo to be defined
Foo* pFoo; // only requires the compiler to know that Foo is a class
pFoo = new Foo(); // requires Foo to be defined
|
1 2 3 4
|
void func() {
vector<Foo> foos; // requires only the compiler to know Foo is a class
vector<Foo> foos(42); // requires definition of Foo [makes 42 objects]
}
|
___________________________________
It's not specifically about using header files. You can logically combine everything into one file if you chose to to.
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
|
// Example program
#include <vector>
class Student;
class Classroom {
public:
std::vector<Student> students;
const Student& get_student();
};
class Student {
public:
Classroom* pClassroom = nullptr;
};
const Student& Classroom::get_student()
{
return students[0];
}
int main()
{
Classroom classroom;
{
Student student;
classroom.students = { student }; // copying Student into classroom.students
}
const Student& the_student = classroom.get_student();
}
|