Problems deriving from std::ostringstream

Discussion in 'C++' started by Adrian, May 8, 2009.

  1. Adrian

    Adrian Guest

    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;
    }
     
    Adrian, May 8, 2009
    #1
    1. Advertising

  2. Adrian

    James Kanze Guest

    On May 8, 4:57 pm, Adrian <> wrote:

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

    --
    James Kanze (GABI Software) email:
    Conseils en informatique orientée objet/
    Beratung in objektorientierter Datenverarbeitung
    9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34
     
    James Kanze, May 9, 2009
    #2
    1. Advertising

  3. Adrian wrote:

    > 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.c.l.l.c-c++ FAQ: http://www.comeaucomputing.com/learn/faq
    c.l.c FAQ: http://c-faq.com/
    c.l.c++ FAQ: http://www.parashift.com/c -faq-lite/
     
    Bart van Ingen Schenau, May 9, 2009
    #3
  4. Adrian

    Adrian Guest

    On May 9, 5:51 am, Bart van Ingen Schenau <>
    wrote:

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

    Thanks again.

    Adrian
     
    Adrian, May 11, 2009
    #4
    1. Advertising

Want to reply to this thread or ask your own question?

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Chris
    Replies:
    3
    Views:
    2,040
    Chris
    Feb 17, 2004
  2. Julian
    Replies:
    2
    Views:
    848
    =?iso-8859-1?q?Stephan_Br=F6nnimann?=
    Dec 14, 2004
  3. Jason Heyes
    Replies:
    1
    Views:
    2,670
    Shezan Baig
    Feb 6, 2005
  4. Bala2508
    Replies:
    28
    Views:
    1,222
    Jim Langston
    Nov 3, 2007
  5. Pallav singh
    Replies:
    3
    Views:
    4,282
    Saeed Amrollahi
    Oct 21, 2009
Loading...

Share This Page