Saving old stream format and restoring it

M

Marcus Kwok

How do you save the formatting options for a stream? In the program
below, notice that when I define my operator<<() for the custom type, it
permanently changes the output format of the stream:

#include <iostream>
#include <iomanip>

struct Data {
double d;
};

std::eek:stream&
operator<<(std::eek:stream& o, const Data& d)
{
//std::eek:stream old;
//old.copyfmt(o);
o << "during: " << std::fixed << std::setprecision(3) << d.d;
//o.copyfmt(old);
return o;
}

int main()
{
Data d;
d.d = 3.14159;

double dd;
dd = 3.14159;
std::cout << "before: " << dd << '\n';
std::cout << d << '\n';
std::cout << " after: " << dd << '\n';

return 0;
}

output:
before: 3.14159
during: 3.142
after: 3.142


When I uncomment the lines in operator<<(), I get a "no appropriate
default constructor available" error, and when I tried

std::eek:stream old(o);

I get a "no copy constructor available or copy constructor is declared
'explicit'" error.


So, how do I save the old formatting options for the stream, and restore
them before returning?
 
K

Kai-Uwe Bux

Marcus said:
How do you save the formatting options for a stream? In the program
below, notice that when I define my operator<<() for the custom type, it
permanently changes the output format of the stream:

#include <iostream>
#include <iomanip>

struct Data {
double d;
};

std::eek:stream&
operator<<(std::eek:stream& o, const Data& d)
{
//std::eek:stream old;
//old.copyfmt(o);
o << "during: " << std::fixed << std::setprecision(3) << d.d;
//o.copyfmt(old);
return o;
}

int main()
{
Data d;
d.d = 3.14159;

double dd;
dd = 3.14159;
std::cout << "before: " << dd << '\n';
std::cout << d << '\n';
std::cout << " after: " << dd << '\n';

return 0;
}

output:
before: 3.14159
during: 3.142
after: 3.142


When I uncomment the lines in operator<<(), I get a "no appropriate
default constructor available" error, and when I tried

std::eek:stream old(o);

I get a "no copy constructor available or copy constructor is declared
'explicit'" error.


So, how do I save the old formatting options for the stream, and restore
them before returning?

Just don't change them:

#include <iostream>
#include <iomanip>
#include <sstream>

struct Data {
double d;
};

std::eek:stream&
operator<<(std::eek:stream& o, const Data& d)
{
std::stringstream str;
str << "during: " << std::fixed << std::setprecision(3) << d.d;
o << str.str();
return o;
}

int main()
{
Data d;
d.d = 3.14159;

double dd;
dd = 3.14159;
std::cout << "before: " << dd << '\n';
std::cout << d << '\n';
std::cout << " after: " << dd << '\n';

return 0;
}


Best

Kai-Uwe Bux
 
D

Dietmar Kuehl

Marcus said:
std::eek:stream&
operator<<(std::eek:stream& o, const Data& d)
{
//std::eek:stream old;

Use 'std::eek:stream old(0);' instead: the constructor of 'std::eek:stream'
requires a stream buffer but this can be null.
 
D

Dietmar Kuehl

Kai-Uwe Bux said:
Just don't change them:
std::eek:stream&
operator<<(std::eek:stream& o, const Data& d)
{
std::stringstream str;
str << "during: " << std::fixed << std::setprecision(3) << d.d;
o << str.str();
return o;
}

This doesn't work: for example, when called using code like this
it behaves in unexpected ways:

std::cout << std::showpos << d;
 
N

Neil Cerutti

Use 'std::eek:stream old(0);' instead: the constructor of 'std::eek:stream'
requires a stream buffer but this can be null.

I was going to suggest:

std::eek:stream::ios_type old;

The docs say it can't be used until init is called, but it's not clear
if that means you can't simply copy format information in and then out
again.

If you wind up needing to call init before copyfmt, then yucky.
 
D

Dietmar Kuehl

Neil said:
I was going to suggest:

std::eek:stream::ios_type old;

I have no idea what the above is...? Are you referring to
'std::ios_base'? This cannot hold the complete format and I
don't think it is equipped with a 'copyfmt()' member function.
The other candidate would be 'std::ios'. However:
If you wind up needing to call init before copyfmt, then yucky.

The design of the initialization of 'std::ios_base' and 'std::ios'
sucks^H^H^H^H^His suboptimal and you indeed need to call 'init()'
before you can do anything to 'std::ios'. In particular, a call
to 'init()' is strictly required even to destruct the object (the
standard is explicit about this in lib.basic.ios.cons paragraph 2,
second sentence).
 
M

Marcus Kwok

Dietmar Kuehl said:
Use 'std::eek:stream old(0);' instead: the constructor of 'std::eek:stream'
requires a stream buffer but this can be null.

Nice, this seems to work great. Thanks.
 
D

Dietmar Kuehl

Marcus said:
Nice, this seems to work great. Thanks.

Note, however, this creation of a stream object is relatively
involved. At the very least it initialized quite a few member
variables and it might even do memory allocations. Thus, if you
need to save the format frequently you might want to consider
using just one stream object, e.g. by using a function static
object:

{
static std::eek:stream old(0);
...
}

This object will only be constructed once. Of course, it will
also cause problems in multi-threaded code.
 
N

Neil Cerutti

I have no idea what the above is...? Are you referring to
'std::ios_base'? This cannot hold the complete format and I
don't think it is equipped with a 'copyfmt()' member function.
The other candidate would be 'std::ios'. However:

Yes, thanks for the correction. I had meant to use a basic_ios type.
The design of the initialization of 'std::ios_base' and 'std::ios'
sucks^H^H^H^H^His suboptimal and you indeed need to call 'init()'
before you can do anything to 'std::ios'. In particular, a call
to 'init()' is strictly required even to destruct the object (the
standard is explicit about this in lib.basic.ios.cons paragraph 2,
second sentence).

So there's a default constructor because...

No wonder Stroustrup doesn't cover this stuff. ;-)
 
M

Marcus Kwok

Dietmar Kuehl said:
Note, however, this creation of a stream object is relatively
involved. At the very least it initialized quite a few member
variables and it might even do memory allocations. Thus, if you
need to save the format frequently you might want to consider
using just one stream object, e.g. by using a function static
object:

{
static std::eek:stream old(0);
...
}

This object will only be constructed once. Of course, it will
also cause problems in multi-threaded code.

Oh, I see. So, for example, if I had a

std::vector<Data> v;

and did a

std::copy(v.begin(), v.end(), std::eek:stream_iterator<Data>(std::cout, "\n"));

then it would have to create/destroy a new std::eek:stream for every
element in the vector, correct? That does seem like quite a bit of
overhead.

The static member/multithreading issue is also significant, but since
that is OT here (until they add concurrency to the standard) I can defer
that argument to another place and time.

It would be nice if the standard had a lightweight "format" class to
store this in.

This question was mainly academic for me, and due to the two issues
above, it looks like it may not be possible to have a completely
satisfactory solution that can be used in production code. I guess I
should just stick to explicitly formatting where needed.
 
K

Kai-Uwe Bux

Dietmar said:
This doesn't work: for example, when called using code like this
it behaves in unexpected ways:

std::cout << std::showpos << d;

Good catch. Thanks.

So, what about:

std::eek:stream&
operator<<(std::eek:stream& o, const Data& d)
{
std::stringstream str;
str.copyfmt(o);
str << "during: " << std::fixed << std::setprecision(3) << d.d;
o << str.str();
return o;
}

Although, that is probably less efficient than saving the old streams state
and reinstalling it afterwards.


Best

Kai-Uwe Bux
 
D

Dietmar Kuehl

Marcus said:
Oh, I see. So, for example, if I had a

std::vector<Data> v;

and did a

std::copy(v.begin(), v.end(), std::eek:stream_iterator<Data>(std::cout,
"\n"));

then it would have to create/destroy a new std::eek:stream for every
element in the vector, correct? That does seem like quite a bit of
overhead.

Indeed. Of course, the above vector is empty so no harm is done :)
The static member/multithreading issue is also significant, but since
that is OT here (until they add concurrency to the standard) I can defer
that argument to another place and time.

I haven't played with it but in theory, at least, you could use a
pointer to stream in thread local storage. In addition, you might,
of course, put the state restoration code not into the formatting
function but into the calling function.
It would be nice if the standard had a lightweight "format" class to
store this in.

Yes, indeed. Boost has apparently something but I haven't used that
class either.
 
D

Dietmar Kuehl

Kai-Uwe Bux said:
So, what about:

std::eek:stream&
operator<<(std::eek:stream& o, const Data& d)
{
std::stringstream str;
str.copyfmt(o);
str << "during: " << std::fixed << std::setprecision(3) << d.d;
o << str.str();
return o;
}

Although, that is probably less efficient than saving the old streams
state and reinstalling it afterwards.

Indeed: it even constructs at least one more object requiring
dynamic memory allocation (the string). In fact, with the given
changes of merely setting the formatting flags and the precision,
it might be best to simply restore just them:

std::eek:stream&
operator<< (std::eek:stream& o, Data const& d)
{
std::ios_base::fmtflags flags = o.setf(std::ios_base::fixed);
std::streamsize precision = o.precision(3);
o << "during: " << d.d;
o.precision(precision);
o.flags(flags);
}
 
M

Marcus Kwok

Dietmar Kuehl said:
Indeed: it even constructs at least one more object requiring
dynamic memory allocation (the string). In fact, with the given
changes of merely setting the formatting flags and the precision,
it might be best to simply restore just them:

std::eek:stream&
operator<< (std::eek:stream& o, Data const& d)
{
std::ios_base::fmtflags flags = o.setf(std::ios_base::fixed);
std::streamsize precision = o.precision(3);
o << "during: " << d.d;
o.precision(precision);
o.flags(flags);
}

This seems elegant; only saving and restoring the things that get
modified.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,755
Messages
2,569,536
Members
45,009
Latest member
GidgetGamb

Latest Threads

Top