简体   繁体   中英

std::stringstream as parameter

I'm somewhat new to the C++ language. I'm writing a utility class for logging to file. It works beautifully except that now I would like to enhance it by making it more convenient to use (eg pass stringstreams to a log function).

This is what I've been trying and it hasn't worked.

definition:

void LogStream( std::stringstream i_Log ){ m_FileHandle << i_Log << std::endl; }

call:

m_LogObject->LogStream( "MKLBSearchEngine::Search( " << x << ", " << i_Filter << " ) - No Results Found" );

There are several problems with your solution. The first is that you're passing stringstream by value, and it doesn't support copy. You need by reference. The second is that at the call site, the return value of the operator<< overloads is ostream& , not stringstream , and since stringstream isn't a base class of ostream& (it's the other way round), you can't initialize the stringstream (or the stringstream& ) with it. And finally, there's no operator<< which takes a stringstream as the right hand parameter, so the statement in the LogStream function can't work. Finally, this is going to be somewhat awkward for the user anyway. A log of operator<< are non-members, with an ostream& non-const reference as first argument, so you can't call them with a temporary as the left argument. (In your example call, of course, you forgot to create the std::ostringstream anyway; it won't compiler because there is no overload of << which takes a char const[] or a char const* as its left hand operand.)

There are work-arounds for almost all of these problems. Something like:

void LogStream( std::ostream& text )
{
    std::ostringstream& s = dynamic_cast<std::ostringstream&>(text);
    m_FileHandle << s.str() << std::endl;
}

handles all of the problems except the last; the last has to be handled by the client, something like:

m_LogObject->LogStream( std::ostringstream().flush() << "..." << x );

(The call to std::ostream::flush() returns a non-const reference to the stream, which can be used to initialize further std::ostream& . And while you can't initialize a non-const reference with a temporary, you can call a non-const member function on it.)

The awkwardness of this for the client code makes me generally prefer a more complex solution. I define a special LogStreamer class, something like:

class LogStreamer
{
    boost::shared_ptr< std::ostream > m_collector;
    std::ostream* m_dest;

public:
    LogStreamer( std::ostream& dest )
        , m_collector( new std::ostringstream )
        , m_dest( &dest )
    {
    }
    ~LogStreamer()
    {
        if ( m_collector.unique() ) {
            *m_dest << m_collector->str() << std::endl;
        }
    }
    template <typename T>
    LogStreamer& operator<<( T const& value )
    {
        *m_collector << value;
        return *this;
    }
};

and

LogStreamer LogStream() { return LogStreamer( m_FileHandle ); }

The client code can then write:

m_LogObject->LogStream() << "..." << x;

In my own code: the log object is always a singleton, the call is through a macro, which passes __FILE__ and __LINE__ to the LogStream() function, and the final target ostream is a special streambuf with a special function, called by LogStream() , which takes a filename and a line number, outputs them, along with the time stamp, at the start of the next line output, and indents all other lines. A filtering streambuf with something like:

class LogFilter : public std::streambuf
{
    std::streambuf* m_finalDest;
    std::string m_currentHeader;
    bool m_isAtStartOfLine;
protected:
    virtual int overflow( int ch )
    {
        if ( m_isAtStartOfLine ) {
            m_finalDest->sputn( m_currentHeader.data(), m_currentHeader.size() );
            m_currentHeader = "    ";
        }
        m_isAtStartOfLine = (ch == '\n');
        return m_finalDest->sputc( ch );
    }
    virtual int sync()
    {
        return m_finalDest->sync();
    }

public:
    LogFilter( std::streambuf* dest )
        : m_finalDest( dest )
        , m_currentHeader( "" )
        , m_isAtStartOfLine( true )
    {
    }
    void startEntry( char const* filename, int lineNumber )
    {
        std::ostringstream header;
        header << now() << ": " << filename << " (" << lineNumber << "): ";
        m_currentHeader = header.str();
    }
};

(The function now() , of course, returns a std::string with the timestamp. Or a struct tm , and you've written a << for tm .)

You have a problem with your design. You don't want to accept a stream as a parameter, either accept a string, or make your class behave as a stream (or both).

If you make your object behave as a stream, then you do the following:

m_LogObject << "what to log" << etc;

To do that, simply override the << operator.

Your call should look like

m_LogObject->LogStream( stringstream() << "MKLBSearchEngine::Search( " << x
 << ", " << i_Filter << " ) - No Results Found" );

since you need to create your stringstream object that you will be passing to the function.

This call implies that you already have a desired output stream so i'd also recommend you changing your class design to use operator<< for logging unless it is already overloaded.

Your function call won't work, as "MKLBSearchEngine::Search( " is of type const char* and that has no overload for the << operator. It won't work with std::string("MKLBSearchEngine::Search( ") either, as also std::string doesn't have such an operator. What you can do is call it with std::stringstream("MKLBSearchEngine::Search( ") , which converts the first argument to a stream, such that the following operators work on this stream. But as others pointed out, you will have to make the function argument a const reference, as streams are not copyable (even then it would be quite inefficient). Also just writing a std::stringstream into the file won't do what you want (if it works anyway), instead you have to take its contents (the underlying std::string ). So all in all your code should look like:

void LogStream( const std::stringstream &i_Log ){ m_FileHandle << i_Log.str() << std::endl; }
...
m_LogObject->LogStream( std::stringstream("MKLBSearchEngine::Search( ") << x << ", " << i_Filter << " ) - No Results Found" );

But you could also just use a LogString(const std::string &) and let the user of this function call stream.str() himself.

You can't pass stream objects by value (since they are not copyable), so you need to pass by (and store) references:

void LogStream(std::stringstream& i_Log){
    m_FileHandle << i_Log << std::endl;
}

This probably won't do what you're expecting though (it will probably print an address of i_Log, for rather obscure reasons).

If your intention is to take stuff OUT of the stringstream, this might do what you want:

i_Log.get( *m_FileHandle.rdbuf() );

You are passing std::stringstream instance by value. You want to avoid copying and pass it by reference (or pointer). For example:

void LogStream ( std::stringstream & i_Log ){ m_FileHandle << i_Log << std::endl; }

Read more about C++ references .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM