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). 开发者_JAVA技巧
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.
精彩评论