Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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 (e.g. 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" );

like image 837
Nathan Wehr Avatar asked Dec 10 '22 08:12

Nathan Wehr


1 Answers

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.)

like image 168
James Kanze Avatar answered Dec 25 '22 22:12

James Kanze