There are declarations of types, and definitions of types.
If you declare a type, it might not be actually defined:
// declare X
class X;
// define X
class X {
int value;
};
You may create a pointer to a type that has been only declared, but you may not instantiate an object if it is ONLY declared--it must also be defined. (Definitions are also declarations.)
Also, while inside the definition of a type, the type itself is still incomplete. Only after the full definition has completed (think the closing } of the class) then it is a complete type and you may instantiate it.
This prevents things like declaring an object to have an instance of itself in itself.
class Bad {
Bad other; // Invalid: Bad is still an incomplete type
};
class Good {
Good * other; // just a pointer is ok, doesn't need type info
};
Of course, if you have a pointer to an incomplete type, you still are not allowed to dereference it, or call through it, because the compiler doesn't know anything about what's there. If you use the pointer as more than storing an address, you still must have the full definition.
So what's the big fuss? Why declare incomplete types?
In a header, if you only need a pointer to an object, you can avoid including that object's header. That means the file does not depend on the implementation, and anyone modifying the object header will NOT require your code to recompile. If you include the header of another object, you (and anyone that includes you) will need to recompile anytime that header changes. If it's a gratuitous include, it's a slowdown to compile times and a nastier dependency graph in your codebase. So utilizing incomplete types is beneficial, even if it adds a small level of complexity.