In test.h
:
#ifndef TEST_H
#define TEST_H
#include <map>
struct Incomplete;
class Test {
std::map<int, Incomplete> member;
public:
Test();
int foo() { return 0; }
};
#endif
In test.cpp
:
#include "test.h"
struct Incomplete {};
Test::Test() {}
In main.cpp
#include "test.h"
int main() {
Test test;
return test.foo();
}
g++ 4.7 gives me an error that struct Incomplete
is forward declared when I write g++ main.cpp test.h -o main.o
.
However, if I change std::map<int, Incomplete> member
to std::map<int, Incomplete*> member
, main.o
compiles. Why is this?
Why is this?
Because the C++ standard library containers are not defined to work with incomplete member types. This is by design1 – but it’s arguably an error (and might be changed in future versions of C++). The Boost.Containers library fixes this.
Your code with pointers works because a pointer to an incomplete type is itself a complete type. However, this obviously changes the semantics of your type quite drastically (in particular, who manages the memory?) and it’s generally not a good idea to use this as a replacement.
1 It’s worth pointing out that the article claims that you technically cannot implement std::map
to work with incomplete types. However, this claim is wrong.
When you forward declare a type you basically declare that a type with that name exists, nothing more. The compiler does not know its size, members, etc. It is called an incomplete type and cannot be used for anything else than declaring pointers or references. This is possible because the size of a pointer, regardless its type is the same on the same platform.
A forward declaration means "this class/struct/whatever will be in the code later". That container needs to know how big the struct is to do the memory book-keeping. In technical terms, you're using an incomplete type.
Since a pointer is of a fixed size on most machines (x64 has usually 8 byte pointers), you don't need to see the full definition of your struct when compiling since you don't need to "calculate" how much space to reserve for that structure in your map: you just need the space needed for a pointer.