Author Topic: C++ Strings SUCK  (Read 7411 times)

Offline leeor_net

  • Administrator
  • Hero Member
  • *****
  • Posts: 2352
  • OPHD Lead Developer
    • LairWorks Entertainment
C++ Strings SUCK
« on: August 07, 2016, 11:37:17 AM »
Like the big fat D too.

Yeah, everybody knows it. I got sick of having to do this sort of thing for what ought to be very, very basic:

Code: [Select]
void GameState::drawDebug()
{
Renderer& r = Utility<Renderer>::get();

stringstream str;
str << "FPS: " << mFps.fps();
r.drawText(mFont, str.str(), 10, 25, 255, 255, 255);

str.str("");
str << "Map Dimensions: " << mTileMap->width() << ", " << mTileMap->height();
r.drawText(mFont, str.str(), 10, 25 + mFont.height(), 255, 255, 255);

str.str("");
str << "Max Digging Depth: " << mTileMap->maxDepth();
r.drawText(mFont, str.str(), 10, 25 + mFont.height() * 2, 255, 255, 255);

str.str("");
str << "Map Mouse Hover Coords: " << mTileMap->tileMouseHoverX() << ", " << mTileMap->tileMouseHoverY();
r.drawText(mFont, str.str(), 10, 25 + mFont.height() * 3, 255, 255, 255);

str.str("");
str << "Current Depth: " << mTileMap->currentDepth();
r.drawText(mFont, str.str(), 10, 25 + mFont.height() * 4, 255, 255, 255);


str.str("");
str << "Structure Count: " << mStructureManager.count();
r.drawText(mFont, str.str(), 10, 25 + mFont.height() * 6, 255, 255, 255);
}


Back in the 90's when I was building games in QBasic for DOS, I could do things like this:

Code: [Select]
myString = "Tile X: " + tile.x + " Tile Y: " + tile.y

And it worked beautifully.

Bah, fuck C and C++. Something so basic can be so flipping frustrating and leads to retarded code like above.

Set about to fix it. Came up with something super simple using C++ templates that does the job. I'm sure there are ways this can be horribly broken, 100% sure this isn't exception or thread safe but for my purposes it does the trick:

Code: [Select]
template<typename ... Args>
std::string string_format(const std::string& format, Args ... args)
{
size_t size = snprintf(nullptr, 0, format.c_str(), args ...) + 1;
unique_ptr<char[]> buffer(new char[size]);
snprintf(buffer.get(), size, format.c_str(), args ...);
return string(buffer.get(), buffer.get() + size - 1);
}

Works a lot like C's printf function and reduces the above code to this:

Code: [Select]
void GameState::drawDebug()
{
Renderer& r = Utility<Renderer>::get();

r.drawText(mFont, string_format("FPS: %i", mFps.fps()), 10, 25, 255, 255, 255);
r.drawText(mFont, string_format("Map Dimensions: %i, %i", mTileMap->width(), mTileMap->height()), 10, 25 + mFont.height(), 255, 255, 255);
r.drawText(mFont, string_format("Max Digging Depth: %i", mTileMap->maxDepth()), 10, 25 + mFont.height() * 2, 255, 255, 255);
r.drawText(mFont, string_format("Map Mouse Hover Coords: %i, %i", mTileMap->tileMouseHoverX(), mTileMap->tileMouseHoverY()), 10, 25 + mFont.height() * 3, 255, 255, 255);
r.drawText(mFont, string_format("Current Depth: %i", mTileMap->currentDepth()), 10, 25 + mFont.height() * 4, 255, 255, 255);

r.drawText(mFont, string_format("Structure Count: %i", mStructureManager.count()), 10, 25 + mFont.height() * 6, 255, 255, 255);
}

I am unsure of any performance hits this may incur though my intuition tells me it should be roughly the same as it was before, maybe even a bit faster despite the construction/destruction of the string object in the templated function. Any decent optimizing compiler should be able to deal with this well enough and the game continues to perform as it did before. Will profile this later and determine if i need to perhaps optimize this some.
« Last Edit: August 07, 2016, 11:40:18 AM by leeor_net »

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4955
Re: C++ Strings SUCK
« Reply #1 on: August 08, 2016, 03:14:34 PM »
C++ can be very verbose.

Null terminated strings suck. They prevent slicing and re-use without reallocation. They are slow to determine the length of. They have null termination issues that are forever a source of bugs. There's no bounds checking, although that's more a flaw of C++'s array model than strings. The standard library classes do add some safety, but yes, it's still gross and verbose. C++ isn't really the most convenient language for string processing.

Quote
... 100% sure this isn't exception or thread safe ...
Why not?

Formatting strings probably won't be your major performance bottleneck. It can be slow, but unless you're writing hundreds of thousands of lines to the console, it probably won't be that bad.

Offline leeor_net

  • Administrator
  • Hero Member
  • *****
  • Posts: 2352
  • OPHD Lead Developer
    • LairWorks Entertainment
Re: C++ Strings SUCK
« Reply #2 on: August 09, 2016, 12:13:43 AM »
Quote
... 100% sure this isn't exception or thread safe ...
Why not?

Just a sneaking suspicion and my experience tells me to trust my suspicions.

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4955
Re: C++ Strings SUCK
« Reply #3 on: August 11, 2016, 09:22:18 AM »
I have a sneaking suspicion that code is both thread and exception safe.

Offline leeor_net

  • Administrator
  • Hero Member
  • *****
  • Posts: 2352
  • OPHD Lead Developer
    • LairWorks Entertainment
Re: C++ Strings SUCK
« Reply #4 on: August 11, 2016, 10:47:55 AM »
I have a sneaking suspicion you're right.

I don't do a lot of multithreading so I always suspect my code won't work.

As for exception safety, considering that I'm using RAII there should be no memory leaks should an exception be thrown during the 'new' since I'm using a smart pointer.

I just don't like using C API's... something about them feels dirty.

Offline Arklon

  • Administrator
  • Hero Member
  • *****
  • Posts: 1269
Re: C++ Strings SUCK
« Reply #5 on: August 12, 2016, 12:39:26 AM »
I just don't like using C API's... something about them feels dirty.
I'd say using the *printf functions in C++ is a fairly commonplace practice, a lot of people dislike streams.

Offline leeor_net

  • Administrator
  • Hero Member
  • *****
  • Posts: 2352
  • OPHD Lead Developer
    • LairWorks Entertainment
Re: C++ Strings SUCK
« Reply #6 on: August 12, 2016, 01:36:38 AM »
Quote
I'd say using the *printf functions in C++ is a fairly commonplace practice, a lot of people dislike streams.

You're probably right. I like using streams simply because it's a concept that makes a huge amount of sense to me -- e.g., looking at a file or a database or network connection can be seen as simply a stream of bytes (it's a concept I use in NAS2D's Filesystem implementation)... but I'm finding that I may a rare breed in that respect.

Still, string handling in C/C++ sucks.

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4955
Re: C++ Strings SUCK
« Reply #7 on: August 12, 2016, 09:52:26 AM »
*printf functions are much less verbose than the stream alternatives. The stream libraries are particularly bad if you don't import the proper namespace and have to prefix all the symbols. Some might argue that's more clear, or better, but it's just so damn verbose. The *printf functions can also be considerably faster too. Not very safe though.

I find even the C APIs tend to handle files and network traffic in a stream like manner. They just require manually passing a FILE* as the first argument, rather than as a hidden "this" pointer. Is there anything in particular about printf or scanf that are particularly not stream-like? Perhaps it's another property that makes them feel so different.

Offline leeor_net

  • Administrator
  • Hero Member
  • *****
  • Posts: 2352
  • OPHD Lead Developer
    • LairWorks Entertainment
Re: C++ Strings SUCK
« Reply #8 on: August 12, 2016, 10:48:57 AM »
Quote
The stream libraries are particularly bad if you don't import the proper namespace and have to prefix all the symbols.

Depending on what I'm doing I'll either import the namespace or opt to make it clear where I'm getting something from, e.g. std::

Quote
The *printf functions can also be considerably faster too.

Which is why I like them.

Quote
Not very safe though.

Which is why I don't like them.

Quote
Is there anything in particular about printf or scanf that are particularly not stream-like?

You're working with 'buffers' which I suppose can be looked at as a stream but I visualize it as a segment of memory vs. a stream which is just a series of bytes. Technically speaking this is effectively identical but for me it's the way I personally visualize it in my head. I go into a different mode of thinking between memory buffers and streams.

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4955
Re: C++ Strings SUCK
« Reply #9 on: August 13, 2016, 04:35:01 AM »
sprintf uses a buffer. printf is more for streaming output. scanf would be for streaming input.

strstream uses a buffer.

I think the main difference is really just that strstream automatically reallocates the buffer when it needs to grow.

Offline leeor_net

  • Administrator
  • Hero Member
  • *****
  • Posts: 2352
  • OPHD Lead Developer
    • LairWorks Entertainment
Re: C++ Strings SUCK
« Reply #10 on: August 13, 2016, 09:43:21 AM »
There also seems to be a lot of overhead with it and people in general don't like the << and >> operators. I also hear bitching about "Ehhhh! I have to specify the namespace and it's harder to read! Eehhhhh!"

Wow, either put the std:: scope resolution or use "using namespace std", not difficult and ultimately a moot point so i see it as an invalid argument.

My issue with using string streams for formatting text is that I either need to keep a global such as "str_scratch" available and make use of that so I'm not invoking c'tors and d'tors every frame and I have to clear the buffer using '.str("");' which after awhile gets truly ugly. On top of that, in order to make use of it with NAS2D I have to call the 'str()' method and I don't know if that literally creates a new string object or if I just get a reference to an already built string (I'm banking on new object, seems to be how all of this works).

Now granted, this kind of kills my argument against the d'tor/c'tor hits because my new function literally spits out a new string object which 1) invokes both a c'tor /d'tor to create the original string, 2) invokes both a c'tor and d'tor to create the copy that gets poof'd out of the function. A good optimizing compiler should be able to either 1) reduce that to a single c'tor/d'tor or 2) eliminate the need for the c'tor/d'tor entirely, but that's implementation specific and can't be counted on.

To really make my function more efficient I should be spitting out a const char* array but that still means I need to deallocate that somewhere.

I'm starting to go in circles now. I'll optimize it later -- I have a though on how I can eliminate all c'tor/d'tors except for when the program starts/terminates.

Offline Arklon

  • Administrator
  • Hero Member
  • *****
  • Posts: 1269
Re: C++ Strings SUCK
« Reply #11 on: August 13, 2016, 06:55:45 PM »
My issue with using string streams for formatting text is that I either need to keep a global such as "str_scratch" available and make use of that so I'm not invoking c'tors and d'tors every frame and I have to clear the buffer using '.str("");' which after awhile gets truly ugly.
Maybe you could subclass stringstream and add an overload for operator << with some uniquely-typed constant (think like how the std::piecewise_construct constant works for picking out a particular overload of emplace()) called something along the lines of "reset" or "clear" which just calls str("") on the stream object. It'd look less ugly at any rate.
« Last Edit: August 13, 2016, 06:57:50 PM by Arklon »

Offline Hooman

  • Administrator
  • Hero Member
  • *****
  • Posts: 4955
Re: C++ Strings SUCK
« Reply #12 on: August 13, 2016, 10:03:57 PM »
Using global objects means it likely (in this case, definitely) won't be thread safe. Using thread local variables would be safer, but still not re-entrant. Think what would happen if you were building a string, and you made a call to a function to build a sub-string, and it re-used the same buffer as a temporary. Due to unexpected side effects and safety issues, I'd recommend just using local variables unless profiling tells you it's actually important to optimise it.

Quote
... I have to call the 'str()' method and I don't know if that literally creates a new string object or if I just get a reference to an already built string (I'm banking on new object, seems to be how all of this works)
According to http://www.cplusplus.com/reference/sstream/stringstream/str/:
Quote
a string object with a copy of the current contents in the stream buffer
« Last Edit: August 13, 2016, 10:05:40 PM by Hooman »