1.What is Forward Declaration

In C++, classes can be forward-declared if you only need to use the pointer-to-that-class type (since all object pointers are the same size, and this is what the compiler cares about). This is especially useful inside class definitions, e.g. if a class contains a member that is a pointer to another class; to avoid circular references (i.e. that class might also contain a member that is a pointer to this class), we simply forward-declare the classes instead.

Forward declaration of a class is not sufficient if you need to use the actual class type, for example, if you have a member whose type is that class directly (not a pointer), or if you need to use it as a base class, or if you need to use the methods of the class in a method.

According to this, use this as pointer is a must, what if not? Let me try with ImplClass

In file included from ImplClass.cpp:4:0:
ImplClass.h:13:5: error: field ‘FdMember’ has incomplete type
Fd FdMember;
^
ImplClass.cpp: In constructor ‘Impl::Impl(Fd)’:
ImplClass.cpp:10:8: error: class ‘Impl’ does not have any field named ‘FdMember’
FdMember(fd)
^
ImplClass.cpp: In member function ‘int Impl::Process()’:
ImplClass.cpp:18:2: error: ‘FdMember’ was not declared in this scope
FdMember.Print();

As wiki expected, it failed due to the incomplete type definition,

Now let me import one inline setter function which is ‘SetFdMember’ to make it more interesting,

2. How Forward Declaration Implemented

To make it clear about this, we need to have the common knowledge of difference of parameter by reference and parameter by value in function call.

1, Parameter by value, which means we will have one new instance copy constructed at the very beginning of this function is called,

inline void SetFdMember(Fd fd) { //To do };

The will be one instance named ‘fd’ of ‘Fd’ in the scope of function ‘SetFdMember’, which will fully copy constructed before the code in the function is executed. But if ‘Fd’, is only forward declared rather than included, which means we have no ‘Fd’ defination. In this case, there will be no constructor for Fd, and also no requirement of memory that should be allocated for fd.

2. Parameter by reference. Against parameter by value, no instance need to be constructed, we just use and reference, which means, just the instance passed into when called, also,

inline void SetFdMember(Fd&amp; fd) { FdMember = &amp;fd; };

we have only one address assignment to the pointer, which means we just get the address of parameter and give it to this ‘FdMember’, there is nothing more needed other than assign one pointer by address.

It seems to make some sense, and could explain what have happened, but the problem is still there:

We used ‘void*’ instead of ‘include’/forward declaration, now we don’t give a shit to anything about ‘include’/forward declaration, both of them, go to hell. Pointer could be coding lord, I think all the programmers start from C know very clear about that.

But before we build it, ImplClass.cpp and main.cpp need to be updated along with this change,

It passed the build, and give us the result what we expect.
Yes, now what is in your mind is real, that isForward declaration is nothing!
it is just like ‘void*’, the compiler does not know anything about it because there is no definition, just a symbol that let compiler know this one exists, all the implementation are gotten only when we link all the ‘.o'(object) files/’.a'(static lib)/’.so'(dynamic lib).

Obviously, this code in main has one bug, that we need one ‘Fd’ instance to be the input parameter of the setter, but here fd2 is a char one. We passed the build and there is not even a warning due to the use of ‘void*’ and casts. Of course the output value is not valid in this case, in some other case, the program would even crash sometime.
That is another reason why forward declaration is more recommended than ‘void*’. If we are using forward declaration, the program would not even pass the build, error would received and we can target where the bug is.

This is very important for debug. Potential risk of the bugs that could only be found in runtime really sucks, and after spare a entry week debugging, analyze dozen Gs log, review every line int code tree, finally found the bug is caused by this ‘void*’, I promise, you will check all the commit logs on svn/git to find out the author and put a bullet into his head if there is a rifle besides.

Why the output is ’64FF’? It is another long story, I think there will be an answer if we have a concept of ‘sizeof(char)’ and ‘sizeof(int)’.

Now let us try the forward declaration case, of course, before that, we have to revert ‘ImplClass.h’, ‘ImplClass.cpp’ back to forward declaration ones,

Yes, that is what we want to get, the bug would be quite clear at the time we build it, which means, this bug will never exist unless un-builded code is permitted commit. Also, 7-8 bucks is saved if your rifle is AK47-_-!

3. Summary

1, Compared with included, forward declaration has benefits as wiki mentioned or you can just google it.

2, In fact, forward declaration is nothing more than ‘void*’. Although it could be used in varies ways, default usage is preferred except there is dedicated requirement.

3, Compared with ‘void*’, forward declaration is more readable. It is of fundamental importance for programmer that code must be easy to read and understood. Always, co-workers weight more than compiler, customer or pm.

4, Compared with ‘void*’, what’s most important, bugs could be targeted during building time instead of during/after runtime, which really means a lot also for the maintenance and support.