Problems deriving from std::ostringstream

A

Adrian

Hi all,

I am having some problems deriving a logging class from
std::stringstream and I dont understand why it is not work. Main goal
of this I am trying to replace 1000's of lines in some old code that
is of the style
Lock();
stream << "some message";
Unlock();

Main questions are:
1. Why does the temporary version not work with class Wibble (compiler
cannot match an operator<< func)
2. Why does the temporary version output address of char * rather then
the string

Do I need to add operator<< for all the standard types?

[Output]
dluadrianc:/home/adrianc> g++ -g -Wall -ansi -pedantic -Wextra log.cc
dluadrianc:/home/adrianc> a.out
try char* apple try int 23
BEGIN
23
I am wibbles output friend
This is a test
END
BEGIN
42
0x8049540
END

//code
//#include <pthread.h>
#include <iostream>
#include <cstdio>
#include <sstream>
#include <cstdarg>

class LockedLog
{
public:
static LockedLog &Instance()
{
if(instance==0)
{
instance=new LockedLog();
}
return *instance;
}
void log(const char *fmt, ...) //__attribute__((format(printf,
2,3)))
{
va_list ap;
va_start(ap, fmt);
// pthread_mutex_lock(&m_LogMutex);
vprintf(fmt, ap);
// pthread_mutex_unlock(&m_LogMutex);
va_end(ap);
}
private:
LockedLog() { /*pthread_mutex_init(&m_LogMutex, 0);*/ }
static LockedLog *instance;
pthread_mutex_t m_LogMutex;
};

class LogStreamer : public std::eek:stringstream
{
public:
LogStreamer() :m_logger(LockedLog::Instance()) {}
~LogStreamer()
{
m_logger.log("%s", str().c_str());
}
private:
LockedLog &m_logger;
};

LockedLog *LockedLog::instance=0;

class Wibble
{
public:
friend std::eek:stream &operator<<(std::eek:stream &os, const Wibble
&) { os << "I am wibbles output friend"; return os; }
};

int main(int, char *[])
{
LockedLog::Instance().log("try char* %s try int %d\n", "apple",
23);

std::cout << "BEGIN\n";
{
LogStreamer strm;
strm << 23 << std::endl;
strm << Wibble() << std::endl;
strm << "This is a test" << std::endl;
}
std::cout << "END\n";

std::cout << "BEGIN\n";
LogStreamer() << 42 << std::endl;
// doesnt compile
// LogStreamer() << Wibble() << std::endl;
LogStreamer() << "This is NOT a test" << std::endl;
std::cout << "END\n";

return 0;
}
 
J

James Kanze

I am having some problems deriving a logging class from
std::stringstream and I dont understand why it is not work.
Main goal of this I am trying to replace 1000's of lines in
some old code that is of the style
Lock();
stream << "some message";
Unlock();
Main questions are:
1. Why does the temporary version not work with class Wibble
(compiler cannot match an operator<< func)
2. Why does the temporary version output address of char *
rather then the string

The short answer is that the standard streams have identity, and
are not designed to be used as temporary objects. What you need
is a wrapper or handle class which contains a pointer to the
stream and overloads all of the operators.

The literal answer is that some of the operator<< functions are
members, others free functions which take a non-const reference
to the stream as the first argument. Since you can't bind a
temporary to a non-const reference, only the first are taken
into consideration in overload resolution, which leads to some
surprises: the overloads for integral and floating point types
are not members, so aren't found (and result in a compiler
error), and the overload for void* is a member, but the one for
char* isn't, so a string literal finds the first (because of the
implicit conversion of any pointer type to void*), rather than
the second.

Note that this problem only affects the first operation; the
return value of all of the << operators is an ostream&, which
will bind to the ostream&. For a single instance, the simplest
solution is to do something like:

StreamType().flush() << ...

(Traditionally, StreamType() << "" was used, but the standards
committee changed the << operator for char const* from a member
to a free function.)
Do I need to add operator<< for all the standard types?

No. You need a class which doesn't derive from an ostream, but
rather contains a reference to one, and defines all of the <<
operators (by means of a simple template) to the stream it
refers to. If the temporary might be copied (usually the case),
you probably need some sort of reference counting to ensure that
the real "disposal" only occurs when the last instance is
destructed, see OutputStreamWrapper in the IO subsystem at
http://kanze.james.neuf.fr/code-en.html.
 
B

Bart van Ingen Schenau

Adrian said:
Hi all,

I am having some problems deriving a logging class from
std::stringstream and I dont understand why it is not work. Main goal
of this I am trying to replace 1000's of lines in some old code that
is of the style
Lock();
stream << "some message";
Unlock();

Main questions are:
1. Why does the temporary version not work with class Wibble (compiler
cannot match an operator<< func)
2. Why does the temporary version output address of char * rather then
the string

Both questions have the same answer: For both Wibble and char*, the
temporary LogStreamer() object can not bind to the first argument of the
respective operator<< overloads. The reason is that both overloads take
a non-const reference to an ostream, and the language does not allow
binding a temporary object to a non-const reference.
The reason that outputting an integer and an address DO work is because
those operator<< overloads are defined as members of the class ostream,
and the standard does allow you to call a member function on a temporary
object.
Do I need to add operator<< for all the standard types?

No, because that will not help you with user-defined types like Wibble.

Here is an implementation of LogStreamer that can be used like you want
it:

class LogStreamer
{
public:
LogStreamer() :m_logger(LockedLog::Instance()) {}
~LogStreamer()
{
m_logger.log("%s", oss.str().c_str());
}

template<typename T>
std::eek:stream& operator<<(const T& rhs)
{
return oss << rhs;
}

private:
LockedLog &m_logger;
std::eek:stringstream oss;
};

The trick is in the templated operator<<.
As this is a member function, it can be called on a temporary object.
As it returns a reference (which is *never* considered a temporary
object), you can chain it in the normal way for generating output
without problems.

There is one caveat: This template does not work for manipulators, like
std::endl. If you want to be able to stream those *as the first item*
into a LogStreamer, you will need additional overloads of the
LogStreamer::eek:perator<<.

Bart v Ingen Schenau
 
A

Adrian

On May 9, 5:51 am, Bart van Ingen Schenau <[email protected]>
wrote:

Thank you and James for the answers provided. It makes sense now and
code works great.

Thanks again.

Adrian
 

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

Forum statistics

Threads
473,967
Messages
2,570,148
Members
46,694
Latest member
LetaCadwal

Latest Threads

Top