views:

2322

answers:

6

Hello, I'm trying to create proper header files that don't include much other files.
(To keep them clean, to speed up compiling time, ...)

I encountered two problems while doing this:

1 - Forward declaratoin on base classes doesn't work.

class B;

class A : public B
{

    // ...
}

2 - Forward declaration on STD classes doesn't work.

namespace std
{
    class string;
}

class A
{
    string aStringToTest;
}

How do I solve these problems?

+2  A: 

In both cases the compiler needs to know the size of the type. Therefore, a forward declaration will not suffice. A base class could add members or require a virtual table. The string member would require the size of the class to be increase to store the size of the STL string class.

Forward declaring STL classes is often inadvisable since the implementations commonly include explicit template instantiations that speed up compilation.

Judge Maygarden
+13  A: 

The first problem you can't solve.

The second problem is not anything to do with standard library classes. It's because you declare an instance of the class as a member of your own class.

Both problems are due to the requirement that the compiler must be able to find out the total size of a class from its definition.

However, the compiler can work out the size of a pointer to a class, even if it doesn't yet have the full definition of it. So a possible solution in such cases is to have a pointer (or reference) member in the consuming class.

Not much help in the base class case, because you won't get an 'is a' relationship.

Nor is it worth doing for something like std::string. Firstly, it's supposed to be a convenient wrapper around a character buffer, to save you from doing memory management on something so simple. If you then hold a pointer to it, just to avoid including the header, you're probably taking a good idea too far.

Secondly (as pointed out in a comment), std::string is a typedef to std::basic_string<char>. So you need to forward declare (and then use) that instead, by which time things are getting very obscure and hard to read, which is another kind of cost. Is it really worth it?

Daniel Earwicker
Actually TomWij would have a problem even if he used a string pointer or reference. He made the mistake of assuming std::string is a class, which it isn't. It's a typedef of the template basic_string<>, and that can't be forward declared at all.
Shmoopty
@Shmoopty: to be precise it cannot be forward declared because the number of template parameters is undefined in the standard. Implementations can add other template parameters to the one that the language requires.
David Rodríguez - dribeas
And even if you could get those things right, a declaration in namespace std (other than for a specialisation of a template from a standard header) is undefined behaviour. In short, you can forward-declare things from std, and it frequently works, but you shouldn't because it's not defined to work.
Steve Jessop
+2  A: 

You're trying too hard to solve something that isn't actually a problem. Use the header files you need, and reduce - WHERE POSSIBLE - the requirement for them. But don't try and take it to extremes because you'll fail.

In some cases, the PIMPL idiom may help you, but not here.

Roddy
+1  A: 

For your base classes, you need to have the full type definition, not just a declaration. Derived type headers will need to #include the header for their base classes.

For classes in the std namespace, you must include the proper header - <string> in this case - and then do one of 3 things:

  1. Fully qualify the type: std::string aStringToTest

  2. Put a using declaration for just that type: using std::string;

  3. Put in a using declaration for the std namespace: using namespace std;

Harper Shelby
+5  A: 

As answered before by Earwicker, you can not use forward declarations in any of those cases as the compiler needs to know the size of the class.

You can only use forward declaration in a set of operations:

  • declaring functions that take the forward declared class as parameters or returns it
  • declaring member pointers or references to the forward declared class
  • declaring static variables of the forward declared type in the class definition

You cannot use it to

  • declare a member attribute of the given type (compiler requires size)
  • define or create an object of the type or delete it
  • call any static or member method of the class or access any member or static attribute

(did I forget any?)

Take into account that declaring an auto_ptr is not the same as declaring a raw pointer, since the auto_ptr instantiation will try to delete the pointer when it goes out of scope and deleting requires the complete declaration of the type. If you use an auto_ptr in to hold a forward declared type you will have to provide a destructor (even if empty) and define it after the full class declaration has been seen.

There are also some other subtleties. When you forward declare a class, you are telling the compiler that it will be a class. This means that it cannot be a enum or a typedef into another type. That is the problem you are getting when you try to forward declare std::string, as it is a typedef of a specific instantiation of a template

typedef basic_string<char> string; // aproximate

To forward declare string you would need to forward declare the basic_string template and then create the typedef. The problem is that the standard does not state the number of parameters that basic_string template takes, it just states that if it takes more than one parameter, there rest of the parameters must have a default type so that the expression above compiles. This means that there is no standard way for forward declaring the template.

If, on the other hand you want to forward declare a non-standard template (non STL, that is) you can do it for as long as you do know the number of parameters:

template <typename T, typename U> class Test; // correct
//template <typename T> class Test; // incorrect even if U has a default type

template <typename T, typename U = int> class Test {
   // ...
};

At the end, the advice that was given to you by Roddy: forward declare as much as you can, but assume that some things must be included.

David Rodríguez - dribeas
+2  A: 

> Seems that forward declaration is useless for base classes and stl classes.

Correction... Forward declaration is INAPPROPRIATE for base classes and object member. (It's not "useless", it is "inapplicable".)

A base class MUST be declared (not forward declared) when being declared as a based class of another class.

An object member MUST be declared (not forward declared) when being declared by another class, or as parameter, or as a return value. NOTE: by-reference or by-pointer does not have that constraint.

Correction... Forward declaration of STL classes is -- as per ISO 14882 -- undefined behavior. http://www.gotw.ca/gotw/034.htm

Eljay