Sign in to follow this  
Followers 0
Servant of the Lord

Way to pre-declare standard templated class types?

12 posts in this topic

For compile time optimizations, I'd like a way to pre-declare std::string, and some common container classes I use alot. Specifically, std::vector<std::string>, and to a lesser extant, std::map<std::string, std::string>.

 

Since I can't go like this:

namespace std { class string; }

(Because std::string is really the templated type std::basic_string<char>)

 

I was wondering what you gentlemen think about something like this:

class MyString; //Pre-declaration in the headers that need it.
 
void myFunc(MyString &myString);
 
//Later, the actual definition:
class MyString : public std::string { };

 

Since my entire code-base would follow this usage of std::string and std::vector<std::string>, the direct conversion of std::string into MyString wouldn't be a problem. Though, if it was, I could make MyString have an explicit std::string constructor and implicit std::string castability, and when C++11's constructor inheritance is added to MinGW (in the upcoming 4.8), do that.

 

 

On a scale of 1-10, how much does this make you want to hang me? laugh.png

 

Are there any ramifications or side-effects I'm not realizing, in doing this?

0

Share this post


Link to post
Share on other sites

I'm sure you know you these standard classes don't have virtual destructors, and I'm sure you know this means you can't delete a polymorphic pointer to your MyString. People often bring this up as a huge issue when it comes to inheriting from standard classes, and while it's entirely valid, I don't think it's a realistic problem for any half-sane programmer. So in short, there isn't a real technical problem.

 

However, I want to hang you on a scale of about 6. Reducing build times is great and all, but I'd personally rather have longer build times than using custom classes simply for the sake of forward declaration.

 

Particularly, MyString uses camel casing, unlike any of its member functions. I hate that inconsistency. And sure, you can provide functions so I can use std::string instead of MyString, but still, I'd personally prefer MyString to serve more of a purpose than simply forward declaration. If that's its only purpose, I'd prefer the longer build times and not use it.

3

Share this post


Link to post
Share on other sites

Usually precompiled headers are brought up a lot with template heavy code to reduce compile times. Is that not an option or are build times still too long?

1

Share this post


Link to post
Share on other sites

Why not just forward declare std::string?

 

namespace std
{
template < typename T > struct char_traits;
template < typename T > class allocator;
template
<
    typename charT,
    typename traits,
    typename Alloc
> class basic_string;

typedef basic_string<char, char_traits<char>, allocator<char> > string;
}
 
// type now valid
const std::string* ptr = 0;

// before including the actual header files
#include <string>

 

the code above was tested with gcc 4.42. Your implementation may vary

1

Share this post


Link to post
Share on other sites
Doing so is non-portable. Adding a declaration to namespace std is undefined behavior according to the C++ standard, which includes forward declarations of standard library classes.
2

Share this post


Link to post
Share on other sites

Are there any ramifications or side-effects I'm not realizing, in doing this?

What on earth are you trying to accomplish with this ungodly hack?

 

 - If you are worried about compile times, a pre-compiled header should fix that.

 - If you are worried about typing, a typedef should fix that.

 - If you are trying to solve circular dependencies... SAY WHAT?

1

Share this post


Link to post
Share on other sites

I'm not worried about typing, I prefer names like 'std::string' over names like 'string'.

And it's definitely not circular dependencies. laugh.png

 

It's just compile times - I've tried pre-compiled headers, several times, but every time so far, the compiler (MinGW) would sporadically crash halfway through the build (the compiler itself, not the compiled code), and it'd take down the IDE (QtCreator) with it. QtCreator has support for pre-compiled headers, and I followed the QtCreator documentation for pre-compiled headers with MinGW. It just was unstable. When it worked (every 2 out of 3 compiles), it greatly reduced compile times. But when it failed, I had to End Task mingw32-make and then restart my IDE - which was very upsetting to my workflow.

 

I hadn't considered the lack of virtual destructors actually, but I hadn't intended to use these polymorphically. 

 

I didn't really want to rename std::string anyway - it makes it harder for others to read the code. I do have tons (several dozen) of std::string helper functions in a separate namespace (String::blah()), but as much as I'd like them in some String class for convenience, it'd make the class too much of a monolith, so they're probably better off as standalone functions.

 

I'll try RobTheBloke's method - even if it's non-standard, if it works with MinGW and GCC on most platforms, I'd be fine with it. Is the worse case scenario (on sane compilers), a failure to compile or is it likely that something harder to detect would result? Maybe accidentally declaring new 'standard' classes (but without definitions) by mistake?

 

I wonder if GCC has a non-standard built-in pre-declaration header somewhere that it uses internally - I'll look around.

0

Share this post


Link to post
Share on other sites

the compiler (MinGW)

I think I found your problem blink.png

 

But in all seriousness, I assume you are using a recent (4.7.2) build of GCC?

0

Share this post


Link to post
Share on other sites

Yep:

 

g++ (Built by MinGW-builds project) 4.7.2
Copyright (C) 2012 Free Software Foundation, Inc.
0

Share this post


Link to post
Share on other sites

Doing so is non-portable.

 

The same is also true of #pragma once, un-named unions/structs, SIMD optimisations, fread/fwrite, graphics API's, sound API's, input API's, wide strings, C++ 0x, C++ 11, GUI code, and more or less everything that most game developers use on a daily basis. This is a game developer forum, not comp.lang.c++. If you want program truly portable games, you're choices are: text adventures, and text adventures. If you have any aspirations beyond that, well you're just going to have to accept that your codebase will contain a number of platform specific headers. Your compiler may vary, as will the implementation of the standard library, hence the caveat I posted above. 

 

 

Adding a declaration to namespace std is undefined behavior according to the C++ standard, which includes forward declarations of standard library classes.

 

Yes, technically speaking I could write a compiler in which the std lib is implemented as intrinsics, rather than C++ which is treated as a typical library. I doubt you would use it, and I doubt anyone else would either (me included). If the standard lib provided with your compiler is written in C++, it follows the rules of C++. Since forward declarations are legal in C++, it will obey those rules. Or do you disagree?

 

 

Is the worse case scenario (on sane compilers), a failure to compile or is it likely that something harder to detect would result?

 

Failure to compile is the worst that can happen. Annoying, but fixable.

 

I hadn't considered the lack of virtual destructors actually, but I hadn't intended to use these polymorphically.

 

If all you're doing data wise is this:

 

struct String : public std::string
{
   int pod_data_only;
};

 

there is no problem. If you're doing this:

 

struct String : public std::string
{
   std::vector< std::map< std::string, std::vector<int> > > dynamic_data;
};

 

Then be afraid! be very afraid!

 

- If you are worried about compile times, a pre-compiled header should fix that.

Not always an option. You need a compiler that supports them (PS3? Wii?).

 

 

- If you are trying to solve circular dependencies... SAY WHAT?

It happens, especially when working in large teams. It shouldn't happen, and there is usually a way to fix them 'properly', but when deadlines are tight, it's usually better to get it out of the door no matter what, than refactor vasts swathes of code (and go through another round of QA/bug-fixing/delayed release).

Edited by RobTheBloke
1

Share this post


Link to post
Share on other sites

I wonder if GCC has a non-standard built-in pre-declaration header somewhere that it uses internally - I'll look around.

 

<bits/strfwd.h> in GCC and MinGW pre-declares std::string, std::wstring, std::u16string, std::u32string. Though it says:
 

/** @file bits/stringfwd.h
* This is an internal header file, included by other library headers.
* Do not attempt to use it directly. @headername{string}
*/

 
Based on RobTheBloke's code a few posts up, this is the header I'm going to start using: (works with MinGW 4.7.2)

#ifndef COMMON_SYSTEM_FWDSTRING_H
#define COMMON_SYSTEM_FWDSTRING_H

/*
	Forward-declares std::string, StringList, and StringMap.
	
	WARNING: This is non-standard.
*/

namespace std
{
	//----------------------------------------------------------------------
	//	Forward-declare std::string (and std::char_traits, and std::allocator)
	
	template < class T > struct char_traits;
	template < typename T > class allocator;
	template < typename CharT, typename Traits, typename Alloc = allocator<CharT> > class basic_string;

	typedef basic_string<char,		char_traits<char>,		allocator<char> >		string;
	typedef basic_string<wchar_t,	char_traits<wchar_t>,	allocator<wchar_t> >	wstring;
	typedef basic_string<char16_t,	char_traits<char16_t>,	allocator<char16_t> >	u16string;
	typedef basic_string<char32_t,	char_traits<char32_t>,	allocator<char32_t> >	u32string;

	//----------------------------------------------------------------------
	//	Forward-declare std::vector.

	template < class T, class Alloc > class vector;
	
	//----------------------------------------------------------------------
	//	Forward-declare std::less, std::pair, and std::map.

	template <class T> struct less;
	template <class T1, class T2> struct pair;

	template < class Key, class T, class Compare, class Alloc > class map;
	
	//----------------------------------------------------------------------
}

typedef std::vector<std::string, std::allocator<std::string> > StringList;
typedef std::map<std::string, std::string, std::less<std::string>, std::allocator<std::pair<std::string, std::string> > > StringMap;

#endif // COMMON_SYSTEM_FWDSTRING_H

 
GCC will lets me pre-declare the templated classes, but won't let me have default template arguments, because later when the actual templated class is defined, it complains about "redefinition of default argument", which is why I manually declared std::basic_string<char, char_traits<char>, allocator<char> >, instead of just std::basic_string<char>.

0

Share this post


Link to post
Share on other sites


- If you are trying to solve circular dependencies... SAY WHAT?

It happens, especially when working in large teams. It shouldn't happen, and there is usually a way to fix them 'properly', but when deadlines are tight, it's usually better to get it out of the door no matter what, than refactor vasts swathes of code (and go through another round of QA/bug-fixing/delayed release).


I was specifically referencing circular dependencies with respect to the standard library - you can't really cause that without editing your standard library headers...
0

Share this post


Link to post
Share on other sites
#include <iosfwd>

That is at least standard and you would get the string indirectly too.

 

For everything else just try to move as many includes into the .cpp files and remove not needed and redundant headers that get included indirectly already.

Then look if you can not show some of your own types in some headers if you only need references or pointers.

Then maybe use abstract base classes and only include all low level stuff in a derived class.

Oh and try to not include windows.h in your header files and define that WIN32_LEAN_AND_MEAN before that so there is less things included.

2

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!


Register a new account

Sign in

Already have an account? Sign in here.


Sign In Now
Sign in to follow this  
Followers 0