Re: [C++] ostrstream vs. sprintf

From: Henrik Stuart (hstuart@geocities.com)
Date: 02/07/02


Greetings,

On Thursday, February 07, 2002 4:31:36 AM George Greer wrote:
> On Wed, 6 Feb 2002, Henrik Stuart wrote:

>>   However, the benefit of cout over the printf-family is... type
>>   safety and overloading. :o)
>>

> Unless you want to be notified that you're no longer printing an integer
> but now a float.  Perhaps in 'a == b' cases where comparing a floating
> point number is bad?  It's all fuzzy and some cases get handled by the
> compiler, such as GCC's -Wfloat-equal.  Nothing like type-safety in your
> *printf functions griping at you (via compiler) for putting the wrong types
> in there.

   The majority of compilers today do not notify of non POD-types[1]
   being passed to vararg functions (I know gcc does produce a
   low-level warning on this).

   Now, consider:
   const char* abc = "abcdefg";
   fprintf(fp, "%s\r\n", abc);

   then change abc to:
   const std::string& abc = "abcdefg";
   fprintf(fp, "%s\r\n", abc);

   It's a rather cumbersome process locating these unless you happen
   to like to use gcc and use -Wall and whatnot. :o)
   Hence, I prefer just to use iostreams and let the type system
   handle the rest of the worries - cleaner, less of a headache and
   less modification when you make a transition from char* to
   std::string, which is an obvious thing to do with CircleMud.

> Cheesy example follows:
[snip]

   I didn't say that it wasn't possible to wrap all of this in a
   sensible function in C, merely said it wasn't as easy. Now, imho,
   the sprintmatrix is by far the worse nightmare to maintain over the
   operator<< version.



> Caveats:

>   1) I'm not sure "snprintf(NULL, 0, ..." would work.  I'd then use a
>      dummy buffer (as small as 1 byte should work) to get the size.

   Snprintf returning the needed buffer size is
   _implementation dependant_ afair. I do not have the most recent
   version of the C standard close at hand, but I took the time to
   investigate the working on snprintf of a few of the Windows
   compilers, and most of them return a negative value if the buffer
   wasn't large enough - imho that alone should be enough to
   discourage you to use it in such a fashion (unless, of course it's
   part of the standard, then it's a shortcoming in the compilers I
   looked at).
   Doing proper dynamic memory management in C is a lot more ugly than
   in an object-oriented language since the ool has data encapsulement
   as an integral part.

>   2) Two passes isn't exactly wonderful.  I'd prefer to pass <string,size>
>      to the function like snprintf does and just punt when running out of
>      room.  Alternatively, you could just worst-case the size and use that.

   Worst-casing an arbitrary matrix class that could potentially be
   containing char*'s for automatic symbolic proofs on its'
   correctness? Now, of course, it's certainly possible if you only
   have int's in your code. *pats his templates fondly*

>   3) This isn't an example of what is good, just that it can be done.

   It can be done, alright, there's no denying that. :o)
   You can also write all your code in machine code - that is rarely
   considered good either these days. :o)

>   4) It's not checking for errors from snprintf() due to MailerCode(tm).

   Errors are strange in the C library - just take atoi for an
   example... it returns 0 when it fails? Hmm... how do I know I
   didn't just read a 0?  I know that linux contains the errno
   variable and Windows has the GetLastError(), but afair not a lot of
   the functions in the standard C library are required to have a
   specific return code on failure - I might of course be wrong, it
   has happened before.

>   5) You could just use something like GLIB's:

   Yup, my point wasn't that C++ is _the best_ language - there are
   things that are more nicely done in other languages, was just
   giving my point of view on the preference to std::stringstream over
   *printf.
   Personally, again, however, I think variable argument lists are
   ugly and evil, not to mention notoriously error prone. :o)

>   6) You'd probably make 'format' part of 'matrix *' if you did it a lot.

   Yep, but again it's based on variable arguments.

   One of the beauties with inheritance in C++ is that you can say -
   when I write to files I want to use a different output format. Then
   you merely declare the sensible output operator:

   std::ofstream& operator<<(std::ofstream& ofs, const Matrix& m) {
     // do some output here in another style
   }

   And you would - as "user programmer" not have to concern yourself
   with reading and/or writing the data, because it's already nicely
   defined for ASCII files. (That is if you declare a sensible input
   operator as well for the Matrix classes).

> And in my code you just change the "format" parameter to pass "%g" instead
> of "%d". As an added bonus, say you wanted brackets around all the
> values..."[%d]" is your friend.  Or perhaps you want them all 5 digits
> wide..."[%5d]", done.

   Granted this wasn't directly possible in the ultra, ultra short
   sample I gave in my mail, but it's rather trivial to extend the
   matrix operator to read the current manipulators and use these
   sensibly. That is you could make it do something like:

   std::cout << std::setw(28) << std::left << my_matrix << std::endl;

   And all the matrix elements would be printed 28 characters wide and
   left-aligned. Anyway, trivial topic for the interested reader. :o)

> NOTE: This doesn't mean I don't want a string library.  In fact, I'd really
> like to have one.  Doesn't the '\0' terminator seem like a crass violation
> of data:metadata to you?  The string's end is encoded within the string
> itself due to the lack of a meta-length field.  I just don't see such
> things as the silver bullet to bad code.

   I think the original motivation for using the null terminator
   rather than a "prefixed" size information (like you have in java)
   was to avoid using extra memory on that and "the null character is
   probably not prone to show up in string literals", or something
   along those lines - I forgot the original reason by K&R.

   Am not saying that using some sort of metadata is preferable or
   not, as long as my standard library is "as efficient as possible"
   and doesn't require me to reinvent the wheel every time I happen to
   have to use something more complex than the rudimentary functions
   in the standard C library.
   Not that it isn't trivial to write linked lists, resizable arrays,
   sets and whatnot, but it's not trivial to implement them in the
   "most efficient way" for everyone, let alone force you to do a lot of
   the work, etc. at many turns.

   Some of the work could be avoided to have to be done repetitiously
   in C using some void* stuff (which I really don't like,
   personally), but merely being able to use the standard template
   library for sane containers is a comfort that shouldn't be
   forgotten when picking a programming language at the current date.

   Granted, one could argue that one should choose Java instead, but I
   happen to like speed, but that seems to be religious grounds for
   Java advocates, so I'll rest my case here. :o)


   [1]: POD-types is primitive old data (ints, chars, shorts, floats,
   doubles, etc.), basically the non-struct/union stuff in C. It is
   therefore not possible to stuff a class through a printf statement
   as it's possible to stuff a class through an operator<<.

--
Yours truly,
  Henrik Stuart (http://www.unprompted.com/hstuart/)

--
   +---------------------------------------------------------------+
   | FAQ: http://qsilver.queensu.ca/~fletchra/Circle/list-faq.html |
   | Archives: http://post.queensu.ca/listserv/wwwarch/circle.html |
   | Newbie List:  http://groups.yahoo.com/group/circle-newbies/   |
   +---------------------------------------------------------------+



This archive was generated by hypermail 2b30 : 06/25/03 PDT