4

I want to remove, if possible, the includes of both <vector> and <string> from my class header file. Both string and vector are return types of functions declared in the header file.

I was hoping I could do something like:

namespace std {
    template <class T>
    class vector;
}

And, declare the vector in the header and include it in the source file.

Is there a reference covering situations where you must include in the header, and situations where you can pull the includes into the source file?

Thomas L Holaday
  • 13,614
  • 6
  • 40
  • 51
bias
  • 1,467
  • 3
  • 19
  • 39
  • 9
    I'm curious as to your objective here - why are you trying to remove these headers? – Michael Kohne May 28 '09 at 13:47
  • 4
    Maybe the legacy has 1000 functions declared in one header file, and each function is implemented in its own implementation file. Now he wants to add function 1001, which will take a const std::string reference. If he puts into the header file, he slows the compilation of 1000 source files that do not use std::string. If he forward declares std::string or uses an alternative which displeases the fussy, he has zero impact on the legacy compilation time and possibly avoids having to justify using a string instead of a char pointer. – Thomas L Holaday May 28 '09 at 14:23
  • Yes, please clarify reason. If it's about build performance, see e.g. Jem's answer. – peterchen May 28 '09 at 14:26
  • 3
    His reason for doing it has no bearing on whether it is possible. – Thomas L Holaday May 28 '09 at 14:35
  • 1
    But knowing it may suggest a better solution than the one he proposes. –  May 28 '09 at 14:44
  • But answering correctly the question if whether it is possible does not preclude a followup with whether you think it is wise or beneficial. "You can avoid including the standard headers by forward declaring your own derived versions, but it is uncommon practice and many presume there must be another way" provides both a concise answer to the question and a nudge toward what you think might be a better solution. – Thomas L Holaday May 28 '09 at 15:22
  • My concern was for both build performance, and the desire to minimize 'hidden' includes for people using the class. Though, as *dribeas* pointed out they would have to include them in the case that I don't ... – bias May 28 '09 at 15:24

12 Answers12

14

You cannot safely forward declare STL templates, at least if you want to do it portably and safely. The standard is clear about the minimum requirements for each of the STL element, but leaves room for implemtation extensions that might add extra template parameters as long as those have default values. That is: the standard states that std::vector is a template that takes at least 2 parameters (type and allocator) but can have any number of extra arguments in a standard compliant implementation.

What is the point of not including string and vector headers? Surely whoever is going to use your class must have already included it since it is on your interface.

When you ask about a reference to decide when to include and when to forward declare, my advice would be: include everything that is part of your interface, forward declare internal details.

There are more issues here that plain compilation performance. If you push the include of a type that is in your public (or protected) interface outside of the header you will be creating dependencies on the order of includes. Users must know that they must include string before including your header, so you are giving them one more thing to worry about.

What things should be included in the implementation file: implementation details, loggers, elements that don't affect the interface (the database connectors, file headers), internal implementation details (i.e. using STL algorithms for your implementation does not affect your interface, functors that are created for a simple purpose, utilities...)

David Rodríguez - dribeas
  • 204,818
  • 23
  • 294
  • 489
  • Old answer, I know, but I think the consensus of the standards committee was that it is *not* okay for an implementation to add extra parameters. They can do things like: `template class vector : private _VectorBase { ... };` to get the same effect. – GManNickG Oct 02 '10 at 18:38
11

With a very few exceptions, you are not allowed to add things to the std:; namespace. For classes like vector and string, you therefore have no option but to #include the relevant Standard header files.

Also, notice that string is not a class, but a typedef for basic_string<char>.

  • The std::vector is missing the allocator template parameter which is mandated by the standard. – Motti May 28 '09 at 13:14
  • You have no tasteful option but to include the standard header files, but there are distasteful options. – Thomas L Holaday May 28 '09 at 14:07
  • 6
    A forward declaration is not adding anything into the std namespace, only telling the compiler about something that is already there. Also, the rule is not so restrictive: you are allowed to add template specializations for your particular types inside the std namespace as long as they comply with the standard. "A program may add template specializations for any standard library template to namespace std. This results in undefined behavior unless the declaration depends on a user-defined name of external linkage and unless the specialization meets the requirements for the original template" – David Rodríguez - dribeas May 28 '09 at 14:15
  • 1
    To be honest, I wasn't clear what the user was trying to do with his example code. He certainly wasn't trying to add a new template specialisation, so I don't think your quote applies. –  May 28 '09 at 14:26
  • 1
    @dribeas: the standard says you can't add declarations to namespace std (except permissible template specializations). If you haven't included any standard headers, then namespace std is empty. A declaration is adding to the namespace, not declaring "something that is already there". That said, it will work on most compilers. So will adding definitions to namespace std. – Steve Jessop May 28 '09 at 16:41
  • 1
    Interesting point 'onebyone' (for lack of a better way of addressing you). The question really is: is a forward declaration 'adding' something to the namespace? I believe not. You are telling the compiler that the forward declared symbol exists somewhere. Of course you can consider that you are injecting something into a previously empty (for the compiler in this compilation unit) namespace. – David Rodríguez - dribeas May 28 '09 at 20:19
6

This won't help for vector or string, but it might be worth mentioning that there is a forward reference header for iostream, called iosfwd.

Fred Larson
  • 60,987
  • 18
  • 112
  • 174
5

There is no simple obvious way to do it (as others have explained it very well).

However these headers should be seen as being part of the language (really!), so you can let them in your own headers without any problem, nobody will ever complain.

If your concern is compilation speed, I encourage you to use pre-compiled header instead and put these std headers in it (among other things). It will significantly increase your compilation speed.

Sorry the for the "real winner is the one who avoid the fight" kind of answer.

Jem
  • 2,255
  • 18
  • 25
4

Standard containers often have additional default template parameters (allocators, etc.) so this will not work. For example, here's a snippet from GNU implementation:


  template<typename _Tp, typename _Alloc = std::allocator<_Tp> >
    class vector : protected _Vector_base<_Tp, _Alloc>
    { ... };
Nikolai Fetissov
  • 82,306
  • 11
  • 110
  • 171
  • 1
    +1, even if the example is bad. That implementation is exactly the standard definition (minimum requirement) for vector. The idea though is sound: the standard explicitly allows implementors to add extra template arguments as long as they have a default value. – David Rodríguez - dribeas May 28 '09 at 13:54
4

This was something I was trying to do earlier too, but this is not possible due to templates.

Please see my question: Forward Declaration of a Base Class

As such headers don't change during your development it's not worth optimizing that anyway...

Community
  • 1
  • 1
Tamara Wijsman
  • 12,198
  • 8
  • 53
  • 82
  • Your question, and the answer had a important difference. You were intending to instantiate the std::string. In the question you posted you could not even use a forwarded class. Here the question deals with the situation where you could forward declare a regular class but you cannot with the STL due to its specific requirements. – David Rodríguez - dribeas May 28 '09 at 13:56
3

If string and vector are used only in signatures of non-public members of you class, you could use the PImpl idiom:

// MyClass.h
class MyClassImpl;
class MyClass{
public:
    MyClass();
    void MyMethod();
private:
    MyClassImpl* m_impl;
};

// MyClassImpl.h
#include <vector>
#include <string>
#include <MyClass.h>
class MyClassImpl{
public:
    MyClassImpl();
    void MyMethod();
protected:
    std::vector<std::string> StdMethod();
};

// MyClass.cpp
#include <MyClass.h>
#include <MyClassImpl.h>

void MyClass::MyMethod(){
    m_impl->MyMethod();
}

You are always including vector and string in the header file, but only in the implementation part of your class; files including only MyClass.h will not be pulling in string and vector.

Paolo Tedesco
  • 55,237
  • 33
  • 144
  • 193
  • This solves a particular scenario, but does not allow for many others. All your objects must be created dynamically, so you are now limiting the use of stack allocated elements. You must create a virtual destructor that will create a vtable even if your class did not require it. There will be a huge amount of boiler plate code just to define the whole interface in the base class and redefine and implement it on derived classes, and all those methods will have to be virtual. Using PIMPL is a little better solution to the non-problem (there is no problem in including and .) – David Rodríguez - dribeas May 28 '09 at 13:52
  • Yes, you are right about the use of PImpl. I'll update the post. – Paolo Tedesco May 28 '09 at 13:56
3

Just include the header in any file where you reference an STL collection.

As others have mentioned, there's not a way to reliably forward declare the STL classes, and even if you find one for your particular implementation, it will probably break if you use a different STL implementation.

If the compilation units don't instantiate the classes, it won't make your object files any bigger.

JohnMcG
  • 8,709
  • 6
  • 42
  • 49
2

WARNING

Expect that doing this will cause uproar.

The language allows you to derive your own classes:

// MyKludges.h
#include <vector>
#include <string>
class KludgeIntVector : public std::vector<int> { 
  // ... 
};
class KludgeDoubleVector : public std::vector<double> {
  // ...
};

class KludgeString : public std::string {
  // ...
};

Change your functions to return KludgeString and KludgeIntVector. Since these are no longer templates, you can forward declare them in your header files, and include MyKludges.h in your implementation files.

Strictly speaking, derived classes do not inherit base class constructors, destructors, assignment operators, and friends. You will need to provide (trivial) implementations of any that you're using.


// LotsOfFunctions.h
// Look, no includes!  All forward declared!
class KludgeString;
// 10,000 functions that use neither strings nor vectors
// ...
void someFunction(KludgeString &);
// ... 
// Another 10,000 functions that use neither strings nor vectors

// someFunction.cpp
// Implement someFunction in its own compilation unit
// <string> and <vector> arrive on the next line
#include "MyKludges.h"
#include "LotsOfFunctions.h"
void someFunction(KludgeString &k) { k.clear(); }
Thomas L Holaday
  • 13,614
  • 6
  • 40
  • 51
  • 1
    It allows you to do that, but certainly doesn't encourage you to. –  May 28 '09 at 13:36
  • the language allows you, but common sense advises against this. – xtofl May 28 '09 at 13:36
  • 1
    This helps the OP not one bit. He's trying to remove the #include of vector and string from the header. If he subclasses them, then uses them, he still has to include the headers. It's also generally a bad idea to subclass most of the STL - it wasn't really designed for that and you tend to create more problems than you solve. – Michael Kohne May 28 '09 at 13:46
  • You should never derive from a class that was not designed to be derived from. Doing so is opening the door to many (subtle and not so subtle) errors. – David Rodríguez - dribeas May 28 '09 at 13:48
  • 2
    He asked if it's possible, not if it's wise. – Thomas L Holaday May 28 '09 at 13:50
  • @Michael Kohne, he most emphatically does not need to include vector and string in the headers declaring his functions which return vectors and strings. He can forward declare his kludges there. – Thomas L Holaday May 28 '09 at 13:52
  • There, I added a bright warning for Neil Butterworth, xtofl, and dribeas and example code for Michael Kohne. – Thomas L Holaday May 28 '09 at 14:06
  • 1
    @dribeas, the Apache C++ guide has examples of how to derive from standard containers. http://stdcxx.apache.org/doc/stdlibug/16-2.html – Thomas L Holaday May 28 '09 at 14:34
  • Of course, Apache's best known product isn't written in C++. I don't see why you think they are a recognised authority on C++ best practice. –  May 28 '09 at 14:49
  • Best practice != existing practice. I don't see why you think the standard containers were not designed to be base classes. – Thomas L Holaday May 28 '09 at 15:10
  • Because they don't have any virtual methods and thus cannot be treated polymorphically. And often, existing practice == bad practice. –  May 28 '09 at 15:18
  • The motive for deriving from a generic class is specialization, not polymorphism. – Thomas L Holaday May 28 '09 at 15:57
1

Maybe you would better use the pimpl idiom: it appears to me that you don't want to expose the implementation of your class to client code. If the vector and string objects are aggregated by value, the compiler needs to see their full declarations.

xtofl
  • 40,723
  • 12
  • 105
  • 192
1

With the exception of adding overloads to std::swap (the only exception I can think of right now), you are generally not allowed to add anything to the std namespace. Even if it were allowed, the actual declaration for std::vector is a lot more complicated than the code in the OP. See Nikolai N Fetissov's answer for an example.

All that aside, you have the additional problem of what your class users are going to do with functions that return a std::vector or std::string. The C++ Standard section 3.10 says that functions returning such objects are returning rvalues, and rvalues must be of a complete type. In English, if your users want to do anything with those functions, they'll have to #include <vector> or <string> anyway. I think it would be easier to #include the headers for them in your .h file and be done with it.

Community
  • 1
  • 1
Michael Kristofik
  • 34,290
  • 15
  • 75
  • 125
  • 1
    It is possible to spare those users of his class who make use only of the methods that have neither vectors nor strings from including or . – Thomas L Holaday May 28 '09 at 15:51
1

I assume your objective here is to speed up compile times? Otherwise I'm not sure why you would want to remove them.

Another approach (not pretty but practical) is to use macro guards around the include itself.

e.g.

#ifndef STDLIB_STRING
#include <string>
#define STDLIB_STRING
#endif

Although this looks messy, on large codebases it does indeed increase the compile times. What we did is create a Visual Studio macro that will automatically generate the guards. We bind the macro to a key for easy coding. Then it just becomes a company coding standard (or habit).

We also do it for our own includes as well.

e.g.

#ifndef UTILITY_DATE_TIME_H
#include "Utility/DateTime.h"
#endif

Since we have Visual Studio helpers to auto-generate the guards when we create our own header files, we don't need the #define. The macro knows it's a internal include because we always use the

#include ""

format for our own includes and

#include <>

for external includes.

I know it doesn't look pretty but it did speed up our compile times on a largish codebase by over 1/2 hour (from memory).

Shane Powell
  • 13,698
  • 2
  • 49
  • 61