Pointers, References and Values by Michael D. Crawford Continued...

When Full Declarations Are Not Required

Sometimes you can use a data type without declaring anything but its name,
thereby avoiding the need to include its header file.

Full declarations to a class whose name is mentioned in some other code are not required when:

That is, the declaration is not required when the compiler does not need to know anything but the name of the class or struct.

The compiler will need to know at least the name of a class anytime it encounters it in your code. This is because anytime the compiler finds a symbol, the symbol must already exist in the compiler's symbol table, else it will emit a fatal syntax error message. The only exception to this is the first time a symbol is legimately seen, that is, when it is declared - at that point the compiler adds the symbol to the symbol table. Declaring an incomplete type says to the compiler "Put this name in your symbol table, it's OK, I'll fully declare it before you need to know anything of substance about it".

It is important not to mix up use of the class and struct keywords. Classes and structs differ only slightly in C++: struct members are public by default, while class members are private by default. The code actually generated by the compiler will be the same for code that uses each, that is, classes and structs are implemented identically at runtime.

The problem is that sometimes the compiler will allow you to say "class AStruct" in a header file when AStruct really is a struct and then #include "AStruct.h" in an implementation file whre you use it as a struct and it will work just fine - until you move over to a different compiler, sometimes even a different version of the same compiler.

But then you switch compilers or switch to a different platform even with a compiler from the same vendor and your program won't build. This gets very confusing, so you should check if your compiler has the option to warn about inconsistent uses of the class and struct keywords and leave it turned on.

A pleasant surprise is that the declaration is not needed when a prototype declares a class or struct type as a return by value. One would think that because the whole object is being passed back then the return value's declaration would be required, but it really is only required by the code that implements the prototyped function and the client code that calls it - in each case so they will have the prototype to the return value's constructor.

For example, the following code compiles cleanly as it stands in Metrowerks Codewarrior 5.3 for Windows:

class Result;
class RefParam;

class User{
   public:
      User( const RefParam &inParam );
      virtual ~User();

      Result MemberFunc();   // One would expect this to require Result's declaration
};

Neither do you require a parameter's class declaration when you pass an object by value in a prototype:

class RefParam;
class ValParam;

class User{
	public:
		User( const RefParam &inParam );
		virtual ~User();

		void UsesValue( ValParam inParam );
};

But you do need to have the class declaration when the object is stored by value in a member variable. This is required in part so that the sizeof() operator will be able to tell how much memory to reserve when you allocate an object of the class that contains it. This code won't compile in CodeWarrior:

class RefParam;
class ValMember;

class User{
   public:
      User( const RefParam &inParam );
      virtual ~User();

   private:

      // Error   : illegal use of incomplete struct/union/class 'ValMember'
      ValMember	mAMember;
};

The above code would require that you #include "ValMember.h", include some other header file that #include's "ValMember.h" or else include ValMember's class declaration bodily in the same header file.

So here is another simple principle, again a first approximation:

You only need to include the header file for a class if you store an object of that class as a member variable which is a whole object - that is, if you store the object by value.

More on Forward Declarations

There are some further complications you will need to understand to be able to use forward declarations to classes comfortably.

First, you can only give a forward declaration for the actual type of a class or struct. You cannot do this for typedef'ed types. The following code does not compile with g++ 2.95.4 on Linux:

class Foo;

void Bar( Foo const &inFoo );

class RealFoo
{
	public:
		RealFoo();
};

typedef RealFoo Foo;  //conflicting types for `typedef class RealFoo Foo'
                      //previous declaration as `class Foo
void Bar( Foo const &inFoo )
{
	Foo myFoo;
}

This bothers me most often when I mention the Standard Template Library string in my headers. One cannot just say class string; because the STL string is a typedef. The real class is a template called basic_string which is set up to allow characters of different sizes and different ways of allocating its storage. The common string typedef is an alias for a basic_string made up of char with the default allocator.

There are a couple of ways you can deal with this. One way that would work with the above code is to give a forward declaration of the original type and then repeat the typedef yourself:

class RealFoo;

typedef RealFoo Foo;

void Bar( Foo const &inFoo );
...

This works but loses the benefits of using typedefs at all - a convenient shorthand that allows one to avoid to writing complicated declarations, and the ability to change the underlying types of variables throughout a program by updating a single typedef.

Another option is to write a small header file that only includes the forward declaration and the typedef, and then to #include that header. This helps considerably, but again there is a maintainance problem in that the typedef may occur in more than one place. There is not much to be done about this for typedef's that occur in code you do not maintain yourself, but if you are the author of the original code you can make life better for yourself and your client code's developers by writing such a header and #includeing it in the real type's header file in place of a typedef in the full header.

For the most part, I recommend that you #include the original header for a typedef rather than to try to avoid the dependency and compile time by working around it. For most projects, this should not be a significant problem.

This leads me to another recommendation, not as strong as a most of my rules:

Avoid creating typedefs in your own code, except private or protected ones for use by a single class or its subclasses.

This is not a strong rule because a really hairy type (like a pointer to member function) that must be referenced frequently is best given a typedef. But I'm strongly opposed to a certain very common practice, typedefing away the need to use "*" or "&" in a declaration, because it dramatically increases dependencies on header files:

struct Foo
{
      int anInt;
};

typedef Foo* FooPtr;                   // Don't do this
typedef Foo& FooRef;               // or this
typedef Foo const& FooConstRef;    // and especially not this.

A large application I once maintained used the above style extensively and required an hour to compile on the fastest (and most expensive) Macintoshes available at the time. The compile time would have been greatly aided by using forward declarations instead of including the headers. However, every header mentioned dozens of typedefed pointer and reference types and so each of them would depend on dozens of other header files, thus creating dependencies and build times that grew geometrically.

I feel it is best for programmers to come to grips with what pointers and references really are, so one should declare a pointer to Foo as "Foo *" rather than trying to give it a candy coating.

I use the stated exception to this rule extensively in my own code. You have to #include a class' header in the class' own implementation and the header files of subclasses, so it does not contribute to extra dependency or compile time to give a typedef that is not in the class' public interface. The most frequent case of this is providing aliases for types that are lengthy to declare such as templates (including STL containers) that are used internally to a class.

// StringHolder.h
#include <string>
#include <vector>

class StringHolder
{
	public:
		StringHolder();
		void Store( std::string const &inString );
		bool Has( std::string const &inLookFor ) const;

	private:
		typedef std::vector< std::string > StringVec;

		StringVec	myStrings;
};

// StringHolder.cpp
#include <StringHolder.h>
#include <algorithm>

StringHolder::StringHolder()
	: myStrings()
{
	return;
}

void StringHolder::Store( std::string const &inString )
{
	myStrings.push_back( inString );
}

bool StringHolder::Has( std::string const &inLookFor ) const
{
	// The alternative would be std::vector< std::string >::const_iterator

	StringVec::const_iterator found( find( myStrings.begin(),
                                               myStrings.end(),
                                               inLookFor ) );

	return found != myStrings.end();
}

You can create incomplete types when the class or struct is in a namespace, but you have to place your forward declaration in the same namespace:

// Bar.h
namespace MikesHax
{
class Bar
{
	public:
		Bar( int inVal );

	private:
		int	mVal;
};
}

// Foo.h
// Note we don't include Bar.h
namespace MikesHax
{
	class Bar;
}

class Foo
{
	public:
		Foo( MikesHax::Bar const &inBar );
};

Unfortunately this does not work to declare a member class (also known as a nested class) as an incomplete type even though the syntax for using them is similar - you can reopen a namespace but not a class.

A member class is a class that exists within the scope of another class. The iterators provided by STL are common examples of member classes; vector< int >::iterator is a member class of vector:< int >.

// Jacob.h
class Jacob
{
	class Member{
		Member();
	};
};

// Sam.h
// Should include Jacob.h but doesn't

class Jacob	// This is an error but the compiler won't catch it here
{
	class Member;
};

class Sam
{
	public:
		Sam( Jacob::Member const &inMember );
};

// Sam.cpp
#include "Sam.h"
#include "Jacob.h"   // The error is caught when processing this include

Sam::Sam( Jacob::Member const &inMember )
{
	return;
}

Compiling the above can be a little confusing because the attempt at declaring Jacob::Member as an incomplete type in Sam.h is legal code as it stands but it does not do what you want - the compiler will take this as a declaration for Jacob's entire class, with an incomplete type declaration for Member. The error will be detected when the compiler encounters Jacob's real declaration while processing the include of Jacob.h in Sam.cpp. G++ helps by telling you where the header was included from, but many other compilers do not and so an error is reported for apparently legal code:

mike@marg:~/Foo$ g++ -c Sam.cpp
In file included from Sam.cpp:3:
Jacob.h:2: redefinition of `class Jacob'
Sam.h:7: previous definition here
Jacob.h:6: confused by earlier errors, bailing out

Bjarne Stroustrup mentions forward declaration of a member class on page 293 of The C++ Programming Language Special Edition but does not discuss all the cases where one might want to use it. It is not possible to avoid the requirement for a member class' full declaration when it is mentioned in places where otherwise an incomplete type for a regular class or struct would be sufficient.

Member classes can be given forward declarations in the real class declaration for the class they are a member of. You can then mention the member class in any code that includes that header file. The normal use of this is for member classes that are used only by the implementation of the containing class - you put the declaration of the member class in the implementation file for the container class. I commonly do this for functionals that I use as parameters to STL algorithms.

Finally, you cannot give forward references for templates at all. I just think that is a real drag. It is especially a problem for group software development where a template library is being developed in parallel with the rest of the application - each template's header must be #included in each header file that mentions the template, so templates contribute more to growth of compile time and dependencies than they should have to.

Multiplying the problem is the fact that few (if any) compilers are capable of allowing a template's implementation to be placed in a separate file that is unseen by client code, which is the normal way for non-templated C++ code. Thus even if the template interface is stable, updates to its implementation will force everyone to rebuild.

The only solution I can offer is to develop template libraries in a separate effort from your application, and provide updates only when absolutely necessary.

next button previous page contents all programming tips titles

Copyright © 2000, 2001, 2002, 2005 Michael D. Crawford. All Rights Reserved.

One Must Not Trifle With Wizards For It Makes Us Soggy And Hard To Light