Skip to main content
gamedev.net
🔒 Locked

String formatting in C++

Started by kamrann Dec 24, 2001 at 12:43 PM 22 replies 900+ views
Original Post
kamrann
kamrann
Anyone know if theres some equivalent to sprintf and CString::Format in c++/stl? It doesnt seem right that I should have to include stdio.h in my c++ app to do such a basic thing. cheers Cameron
Stoffel
Stoffel
You can use a stringstream, which lets you fill a stream with iostream semantics.

Alternately, you can sprintf directly on STL strings:
[soruce]
string s (4096, ''\0''); // create buffer, null-filled
sprintf (&s[0], "Format String %d\n", arg);
[/source]

This is slightly less efficient than operating directly on a buffer since the string has to be initialized to a value before you write on it, but this code will work.
null_pointer
null_pointer
quote:
Original post by Stoffel

This is slightly less efficient than operating directly on a buffer since the string has to be initialized to a value before you write on it...


Construct the string and then use reserve() instead of filling it with dummy values:


#include <string>

using std::string

int main()
{
string s;
s.reserve(4098);
sprintf (&s[0], "Format String %d\n", arg);
}



quote:
Original post by Stoffel

...but this code will work.


It "may" work. std::string''s representation (how it stores data) is not guaranteed to use a large C-style character array. I would recommend using stringstream''s formatting or simply format your own output into a stringstream. Avoid the C-style string operations where possible.
Stoffel
Stoffel
Not to start sparring or anything, ''cause I really admire null_pointers responses in general. However:
quote:

Construct the string and then use reserve() instead of filling it with dummy values:


Even with that code, the string will not behave properly after the sprintf. You''ll have to resize the string to the length you''ve written. Otherwise, the next operation on the string (like s += string ("something new")) will assume s has zero length because you haven''t told it any different. You''d have to reserve, then resize to the new length of the string. I''m not entirely sure, but it may re-zero the memory on the resize after this call. I actually can''t even remember if string has a resize function at this time (on the GF''s computer, and I''ll be damned if I''m going to surf MSDN on dial-up).

Even if this method worked, it is still less efficient than using a stack buffer. std::string dynamically allocates memory whereas a stack buffer is allocated with the rest of the stack-local variables at the beginning of the function call.

quote:

It "may" work. std::string''s representation (how it stores data) is not guaranteed to use a large C-style character array.


It will work. string and vector both guarantee contiguous memory. In fact, they''re the only containers that do make this guarantee.
alexk7
alexk7
No, there is really no such guarantee for std::string. I just read it in Stroustrup''s book.
alexk7
barazor
barazor
ive heard of using this method

  #include <strstream.h>


ostrstream ost;
ost << "hello" << " world\n";
ost.put(0);
char* string = ost.str();
alexk7
alexk7
Never use strstream!
Use stringstream instead (using the "sstream" header). With strstream, you must deal with buffers explicitely instead of using std::string. I'm not sure, but I think you must delete the buffer yourself in some cases... (and, as you wrote, you must explicitely add a null caracter)

  
#include <sstream>
std::ostringstream ost;
ost << "hello" << " world\n";
std::string str = ost.str();


alexk7@alexk7.com

Edited by - alexk7 on December 26, 2001 2:24:26 AM
alexk7
ANSI2000
ANSI2000
Doesn'' t String template allow you to format derectly by doing something like??

String myString;

myString = "Hello World" + 6 + "bla bla" + 66.88;
Stoffel
Stoffel
quote:
Original post by alexk7
No, there is really no such guarantee for std::string. I just read it in Stroustrup''s book.

Dangit, you''re right. All that''s guaranteed is that string::c_str () will give you a C-portable null-terminated string.

null_pointer
null_pointer
quote:
Original post by Stoffel

Even with that code, the string will not behave properly after the sprintf. You''ll have to resize the string to the length you''ve written. Otherwise, the next operation on the string (like s += string ("something new")) will assume s has zero length...


Good point!


quote:
Original post by Stoffel

...You''d have to reserve, then resize to the new length of the string. I''m not entirely sure, but it may re-zero the memory on the resize after this call. I actually can''t even remember if string has a resize function at this time (on the GF''s computer, and I''ll be damned if I''m going to surf MSDN on dial-up).


Just replace the call to reserve(n) with a call to resize(n), and append the null-terminator where it is needed. Why call both reserve and resize? AFAIK, reserve is only useful to cut down on the memory reallocations and iterator invalidations needed when doing large numbers of push_back calls.


quote:
Original post by Stoffel

Even if this method worked, it is still less efficient than using a stack buffer. std::string dynamically allocates memory whereas a stack buffer is allocated with the rest of the stack-local variables at the beginning of the function call.


Probably. If speed is critical, create a char buffer and perform the C-style operations on it. In most circumstances, I should think that std::string with its support for different character types and locales would prove more useful. Hence the phrase "where possible" in my first post.


quote:
Original post by Stoffel

Dangit, you''re right. All that''s guaranteed is that string::c_str () will give you a C-portable null-terminated string.


Same goes for std::vector.
DrPizza
DrPizza
quote:
Same goes for std::vector

I don''t have an up-to-date C++ reference to hand, but I believe this to be incorrect.

I think that std::vector does guarantee being allocated in a contiguous block of memory (or at least, constant-time access to elements which can only be guaranteed through a contiguous block of memory).
char a[99999],*p=a;int main(int c,char**V){char*v=c>0?1[V]:(char*)V;if(c>=0)for(;*v&&93!=*v;){62==*v&&++p||60==*v&&--p||43==*v&&++*p||45==*v&&--*p||44==*v&&(*p=getchar())||46==*v&&putchar(*p)||91==*v&&(*p&&main(0,(char**)(--v+2))||(v=(char*)main(-1,(char**)++v)-1));++v;}else for(c=1;c;c+=(91==*v)-(93==*v),++v);return(int)v;} /*** drpizza@battleaxe.net ***/
Stoffel
Stoffel
quote:
Original post by DrPizza
I think that std::vector does guarantee being allocated in a contiguous block of memory (or at least, constant-time access to elements which can only be guaranteed through a contiguous block of memory).



That was my impression too, but I''ve already been wrong once this thread.

FWIW, "Effective STL" by Meyers, Item 16: "Know how to pass vector and string to legacy APIs"

"The elements in a vector are constrained by the C++ Standard to be stored in contiguous memory, jsut like an array."

The syntax is &v[0] (NOT v.begin ()), but you also have to check if the vector is empty yourself or the results are undefined.

null_pointer
null_pointer
quote:
Original post by Stoffel

"The elements in a vector are constrained by the C++ Standard to be stored in contiguous memory, jsut like an array."


The standard doesn't prescribe a particular representation for each standard container...For a vector, the element data structure is most likely an array. (Bjarne Stroustrop, "The C++ Programming Language", section 17.1.3, page 465), emphasis mine.


quote:
Original post by DrPizza

I think that std::vector does guarantee being allocated in a contiguous block of memory (or at least, constant-time access to elements which can only be guaranteed through a contiguous block of memory).


Constant-time access does not necessarily mean that elements within the representation will contiguous (i.e., a C-style array). Constant-time access relies on the assumption that the same set of calculations can find any element within the container, but it does not assume how far apart they are spaced (contiguity).

Example:


const int values_per_chunk = 32;
const int chunks_per_collection = 16;
 
struct chunk { int elements[values_per_chunk]; int other_data; };

struct collection { chunk chunks[chunks_per_collection]; };
 
int& access(collection& c, int n)
{
// demonstrates constant-time access without contiguity
 
const int chunk = n / values_per_chunk;
const int index = n - chunk * values_per_chunk;
 
return c.chunks[chunk].elements[subindex];
}
 
int main()
{
collection c; // 368 elements (0...367)
 
access(c, 233) = 5;
access(c, 128) = 2;
  
return 0;
}


Look up vector<bool> in "The C++ Programming Language" by Bjarne Stroustrop for a more useful example.

Edited by - null_pointer on December 28, 2001 12:48:36 PM
DrPizza
DrPizza
Yes, I''m not sure what I was thinking.

vector is an abomination, as AFAIK the standard still doesn''t permit proxy classes to be returned from containers. Without them, it''s not a container (because you can''t reference each element individually).

char a[99999],*p=a;int main(int c,char**V){char*v=c>0?1[V]:(char*)V;if(c>=0)for(;*v&&93!=*v;){62==*v&&++p||60==*v&&--p||43==*v&&++*p||45==*v&&--*p||44==*v&&(*p=getchar())||46==*v&&putchar(*p)||91==*v&&(*p&&main(0,(char**)(--v+2))||(v=(char*)main(-1,(char**)++v)-1));++v;}else for(c=1;c;c+=(91==*v)-(93==*v),++v);return(int)v;} /*** drpizza@battleaxe.net ***/
alexk7
alexk7
What is a proxy class ?
alexk7
null_pointer
null_pointer
quote:
Original post by DrPizza

vector is an abomination


std::vector is a good, reasonably fast and efficient, general-purpose container. MSVC's standards compliance is an abomination.


quote:
Original post by DrPizza

...the standard still doesn't permit proxy classes to be returned from containers. Without them, it's not a container (because you can't reference each element individually).


What are you talking about?

Edited by - null_pointer on December 29, 2001 10:05:47 AM
kvh
kvh
i assume he''s talking about vector&ltbool>, the brackets probably got eaten by the board

vector&ltbool> is a vector specialization that actually uses a bitset to store the bools, and uses proxies to operate on them

you can read all about it here:
vector&ltbool> is nonconforming, and forces optimization choice (PDF)
DrPizza
DrPizza
quote:
Original post by null_pointer

What are you talking about?


Ugh, the stupid thing ate my angle brackets. vector<bool> is what I wrote.
char a[99999],*p=a;int main(int c,char**V){char*v=c>0?1[V]:(char*)V;if(c>=0)for(;*v&&93!=*v;){62==*v&&++p||60==*v&&--p||43==*v&&++*p||45==*v&&--*p||44==*v&&(*p=getchar())||46==*v&&putchar(*p)||91==*v&&(*p&&main(0,(char**)(--v+2))||(v=(char*)main(-1,(char**)++v)-1));++v;}else for(c=1;c;c+=(91==*v)-(93==*v),++v);return(int)v;} /*** drpizza@battleaxe.net ***/
null_pointer
null_pointer
Good points regarding vector<bool&gt.

kvh: I do not understand how vector<bool> could use a bitset for storage. vector<bool> is a variable-length bit "container," whereas bitset is a constant-length bit "container."
merlin9x9
merlin9x9
As further proof of there being no guarantee that vector allocates a contiguous block, consider that it provides no function to retrieve a pointer to that block. That would be plenty useful if, say, calling a C function expecting a normal array. But there isn''t such a function, and I''m positive that it''s because the block might not be contiguous. Of course, we all know that a dynamically-allocated array is the most likely implementation; we also know that we must also never make assumptions.

Topic Locked

This topic has been locked by a moderator. New replies are not allowed.

Sign in to reply to this topic.