Why does "class" at the top of a header file work?

Started by
11 comments, last by GameDev.net 15 years, 9 months ago
Basically, if I have the following:

// some header file

class foo;

class bar
{
 foo* f;
};

I've seen this done in larger software projects so I thought I'd give it a shot, but I can't seem to find an explanation why simply putting "class classname" at the top of a header file works whereas trying to include the header file that contains the class declaration occasionally results in complier errors (due to multiple headers trying to include each other).
Advertisement
class foo; is a forward declaration; it just tells the compiler that foo is a class, and you'll give it the definition of it later.

Because the compiler doesn't know the classes size or anything, you can only use pointers or references to the class, and not call any class methods until you include the header.

Usually you'd use the forward declaration in the header, so you don't pull foo's header into that file, and then you'd #include "foo.h" in bar.cpp, so you can call it's methods and so on.
Well, like you said, if you have circular dependendies (foo.h includes bar.h and bar.h includes foo.h), you'll get this after the preprocessing stage:

class foo{    bar* b;  // uh, what is bar?};class bar{    foo* b;  // I know foo!};

So now foo can't find bar. Switch the include order, and bar can't find foo.

The solution is to tell the compiler that there is a class "foo" (or bar) but not declare its contents.

class bar;   // Hey, there's a class named barclass foo{    bar* b;  // Ah, I know that bar is a class, so I know how make a pointer to it};

This only works for pointers, because to define pointers the compiler doesn't need to know the contents of the type.


This is what everyone will say. However, I'm myself a bit confused about one thing: this assumes that pointers are always the same size. In C, yes. In C++, not necessarily, AFAIK. If you have classes with, say, multiple virtual inheritance, pointers to such classes can take up multiple words in certain compilers (MSVC++). So how does this work with forward declarations? How can the compiler be sure about a pointer's size if it knows nothing about the type's inheritance structure?
Million-to-one chances occur nine times out of ten!
Wow, thank you for the fast response! :)

Makes sense now.
Quote:Original post by Mike nl
I'm myself a bit confused about one thing: this assumes that pointers are always the same size. In C, yes. In C++, not necessarily, AFAIK. If you have classes with, say, multiple virtual inheritance, pointers to such classes can take up multiple words in certain compilers (MSVC++). So how does this work with forward declarations? How can the compiler be sure about a pointer's size if it knows nothing about the type's inheritance structure?

A pointer to an object is a pointer to an object. C++ and C are the same in this respect, otherwise there would be no compatibility between billions of lines of C code and C++.

There is no reason why multiple inheritance or virtual inheritance would affect a pointer-to-object size. No so with pointer-to-member, but pointer-to-object is not different than, say, pointer-to-int.

Stephen M. Webb
Professional Free Software Developer

Quote:Original post by Bregma
No so with pointer-to-member,

Ah right, that's what I was thinking of. My bad, thanks :)
Million-to-one chances occur nine times out of ten!
Multiply inherited objects have multiple addresses, indeed (well, sort of). However, it doesn't mean that the size of the pointer grows, or that you need to keep track of multiple addresses. Here's the idea. Let's have a class C which inherits from classes A and B, i.e. class C : public A, public B. Let Base be the base address of an instantiated object of class C. Assuming the compiler places the sub-objects(*) in order, object A will also share the same Base address. Object B will be placed at Base + sizeof (A). How can object C and sub-object A share the same address? Pretty simple, object C is aware of the sub-objects it contains. When accessing functions, it can choose the correct v-table (or the correct part of the v-table if one sees this as one big v-table). Let's now cast a C* pointer to a B* pointer. Since object B is unaware of C's v-table, the compiler needs to change the address when casting, so the correct v-table is chosen when invoking functions. How this is to be achieved is known at compile time. All you need to do is stay away from reinterpret_cast, because it won't bother to switch the address for you.

--
* Possibly an incorrect term. I refer to the objects of classes A and B, which the object of class C inherits.
Everything you just said, Cybernator, is implementation-defined. I've heard that some implementations represent virtual functions as function objects, rather than with a vtable.

Just something to keep in mind.
@Oluseyi: True, but the point was just to give an idea why it's not necessary to "enlarge" the pointer, so I took the liberty to base the rant with a particular implementation in mind. To all C++-standard lawyers, I apologize if this was offending or something.
Quote:Original post by Cybernator
To all C++-standard lawyers, I apologize if this was offending or something.


Heh, I like that phrase. Almost as good as when someone called SiCrane a "C++ athlete".

This topic is closed to new replies.

Advertisement