Effective use of C++ iomanip library

2019-01-24 11:23发布

问题:

I created a Vector class in C++ and it works great for my problems. I am now cleaning it up, and I ran into the following piece of code:

std::ostream& operator<<(std::ostream &output, const Vector &v){
  output<<"["
    <<std::setiosflags(std::ios::right | std::ios::scientific)
    <<std::setw(23)
    <<std::setprecision(16)
    <<v._x<<", "
    <<std::setiosflags(std::ios::right | std::ios::scientific)
    <<std::setw(23)
    <<std::setprecision(16)
    <<v._y<<", "
    <<std::setiosflags(std::ios::right | std::ios::scientific)
    <<std::setw(23)
    <<std::setprecision(16)
    <<v._z<<"]";
  return output;
} 

The code allows to print a vector as std::cout<<v<<std::endl;. Each number has 23 spaces, of which 16 are the decimals. The text is right-aligned so that it will print:

 1.123456123456e+01
-1.123456123456e+01

Instead of

1.123456123456e+01
-1.123456123456e+01

The code seems awfully repetitive. How can you "store" the format (all the setiosflags, setw and setprecision statements) such that you can say something like "print the characters in a standard way, but the numbers with this given format".

Thank you!

Edit

As per Rob Adams' comment, I changed my ugly code (which, as pointed out by others, would mess up the precision for the "next guy") to a more succinct (and correct):

std::ostream& operator<<(std::ostream &output, const Vector &v){
  std::ios_base::fmtflags f = output.flags(std::ios::right | std::ios::scientific);
  std::streamsize p = output.precision(16);
  output<<"["
    <<std::setw(23)<<v._x<<", "
    <<std::setw(23)<<v._y<<", "
    <<std::setw(23)<<v._z
    <<"]";
  output.flags(f);
  output.precision(p);
  return output;
}

回答1:

Only std::setw() is temporary. The other two calls, setiosflags, and setprecision have a permanent effect.

So, you could change your code to :

std::ostream& operator<<(std::ostream &output, const Vector &v){
  output<<"["
    <<std::setiosflags(std::ios::right | std::ios::scientific)
    <<std::setw(23)
    <<std::setprecision(16)
    <<v._x<<", "
    <<std::setw(23)
    <<v._y<<", "
    <<std::setw(23)
    <<v._z<<"]";
  return output;
} 

But now you've borked the flags and precision for the next guy. Try this instead:

std::ostream& operator<<(std::ostream &output, const Vector &v){
  std::ios_base::fmtflags f = output.flags(std::ios::right | std::ios::scientific);
  std::streamsize p = output.precision(16);
  output<<"["
    <<std::setw(23)
    <<v._x<<", "
    <<std::setw(23)
    <<v._y<<", "
    <<std::setw(23)
    <<v._z<<"]";
  output.flags(f);
  output.precision(p);
  return output;
} 

Finally, if you absolutely have to get rid of the duplication of the constant 23, you could do something like this (but I wouldn't recommend it):

struct width {
  int w;
  width(int w) : w(w) {}
  friend std::ostream& operator<<(std::ostream&os, const width& w) {
    return os << std::setw(width.w);
  }
};


std::ostream& operator<<(std::ostream &output, const Vector &v){
  std::ios_base::fmtflags f = output.flags(std::ios::right | std::ios::scientific);
  std::streamsize p = output.precision(16);
  width w(23);
  output<<"["
    <<w
    <<v._x<<", "
    <<w
    <<v._y<<", "
    <<w
    <<v._z<<"]";
  output.flags(f);
  output.precision(p);
  return output;
} 

See also this other question, where they decided that you can't make width permanent.



回答2:

Everything but the setw() actually do that already. They are "sticky".

Your real problem is what happens to the next output after this one...



回答3:

Generally, you don't use the standard manipulators directly. In this case, for example, you might define a manipulator fromVector, and use that:

output << '['
       << fromVector << v.x << ", "
       << fromVector << v.y << ", "
       << fromVector << v.z << ']';

That way, if you want to change the width and precision of elements in the vector, you only have to do it in one place.

In this case, where the manipulator has no arguments, all that's needed is a simple function:

std::ostream& fromVector(std::ostream& stream)
{
    stream.setf(std::ios::right, std::ios::adjustfield);
    stream.setf(std::ios::scientific, std::ios::floatfield);
    stream.precision(16);
    stream.width(32);
    return stream;
}

Of course, this will have changed most of the formatting for any later user. In general, it would be better practice to use a temporary class object which saves the state, and restores it in the destructor. I usually derive my manipulators from something like:

header: class StateSavingManip { public: StateSavingManip( StateSavingManip const& other ) ;

    virtual             ~StateSavingManip() ;
    void                operator()( std::ios& stream ) const ;

protected:
                        StateSavingManip() ;

private:
    virtual void        setState( std::ios& stream ) const = 0 ;

private:
    StateSavingManip&   operator=( StateSavingManip const& ) ;

private:
    mutable std::ios*   myStream ;
    mutable std::ios::fmtflags
                        mySavedFlags ;
    mutable int         mySavedPrec ;
    mutable char        mySavedFill ;
} ;

inline std::ostream&
operator<<(
    std::ostream&       out,
    StateSavingManip const&
                        manip )
{
    manip( out ) ;
    return out ;
}

inline std::istream&
operator>>(
    std::istream&       in,
    StateSavingManip const&
                        manip )
{
    manip( in ) ;
    return in ;
}

source: int getXAlloc() ; int ourXAlloc = getXAlloc() + 1 ;

int
getXAlloc()
{
    if ( ourXAlloc == 0 ) {
        ourXAlloc = std::ios::xalloc() + 1 ;
        assert( ourXAlloc != 0 ) ;
    }
    return ourXAlloc - 1 ;
}
}

StateSavingManip::StateSavingManip()
    :   myStream( NULL )
{
}

StateSavingManip::StateSavingManip(
    StateSavingManip const&
                        other )
{
    assert( other.myStream == NULL ) ;
}

StateSavingManip::~StateSavingManip()
{
    if ( myStream != NULL ) {
        myStream->flags( mySavedFlags ) ;
        myStream->precision( mySavedPrec ) ;
        myStream->fill( mySavedFill ) ;
        myStream->pword( getXAlloc() ) = NULL ;
    }
}

void
StateSavingManip::operator()( 
    std::ios&           stream ) const
{
    void*&              backptr = stream.pword( getXAlloc() ) ;
    if ( backptr == NULL ) {
        backptr      = const_cast< StateSavingManip* >( this ) ;
        myStream     = &stream ;
        mySavedFlags = stream.flags() ;
        mySavedPrec  = stream.precision() ;
        mySavedFill  = stream.fill() ;
    }
    setState( stream ) ;
}

If you do this, you'll have to add parentheses after the manipulator, e.g.:

output << '['
       << fromVector() << v.x << ", "
       << fromVector() << v.y << ", "
       << fromVector() << v.z << ']';

(I'm sure that some clever soul will figure out a way of avoiding them, but they've never bothered me, so I haven't bothered.)