简体   繁体   中英

conditional debug output class with templated operator<<

I'm trying to create a simple qDebug-like class I can use to output debug messages in debug mode, dependant on some debug level passed when calling the app. I liked the ease of use of the QDebug class (which could be used as a std::cerr and would disappear when compiling in release mode). I have this so far:

#ifdef DEBUG
    static int CAKE_DebugLevel;

    class Debug
    {
        Debug( int level ) : m_output( level <= CAKE_DebugLevel ) {}

        template<typename T>
        Debug& operator<<( T )
        {
            if( m_output )
            {
                std::cout << T;
                return *this;
            }
            else
                return *this;
        }
    private:
        bool m_output;
    };
#else // release mode compile
    #define Debug nullstream
#endif // DEBUG

I think a nullstream -kind of thing would be best for the release mode define:

class nullstream{};

template <typename T>
nullstream& operator<<(nullstream& ns, T)
{
    return ns;
}

In main I have for the moment:

#include "Debug.h"

#include <iostream>

int main()
{
    Debug(0) << "Running debug version of CAKE." << std::endl;
}

Which is giving the following error with gcc 4.5.1:

In member function 'Debug& CAKE_Debug::operator<<(T)': expected primary-expression before ';' token (points at the line std::cout << T; )

In function 'int main(int, char**, char**)': expected unqualified-id before '<<' token

I'm sure it's something simple, but all the general template info I've found turns up nothing. Thanks for any help!

If you need more info, please ask.

Your nullstream class has no integral constructor. This means that when you #define Debug nullstream, the compiler can't recognize Debug(0) - that makes no sense. Debug is not a macro that takes arguments, and if you substitute with nullstream, nullstream has no constructor that takes arguments. #define used this way is oh so wrong. You should have something like this:

#ifdef _DEBUG
static int CAKE_Debuglevel;
#endif

class Debug
{
    Debug( int level ) 
#ifdef _DEBUG
    : m_output( level <= CAKE_DebugLevel ) 
#endif
        {}

    template<typename T>
    Debug& operator<<( T t)
    {
        #ifdef _DEBUG
        if( m_output )
        {
            std::cout << t;
            return *this;
        }
        else
        #endif
            return *this;
    }
private:
#ifdef _DEBUG
    bool m_output;
#endif
};

Now your class really will look and act the same in any environment but only output if _DEBUG is defined. I also fixed the bug where you tried to output a type.

Others have pointed out the error with normal objects.

template<typename T> 
Debug& operator<<(T const& value)
{
    if ( m_output )  
    {  
        std::cout << value;  
    }  
    return *this;  
}

But you also need a way to output std::endl and the other manipulators. As the template above will not handle these correctly. For this you will need an operator that handles functors that manipulate streams (ie all the iomanipulators (like std::endl)).

// Use a typedef to make the code readable.
// This is a function pointer that takes a stream as input and returns the stream.
// This covers functions like std::endl
typedef std::ostream& (*STRFUNC)(std::ostream&);

D& operator<<(STRFUNC func)  // Inside the class
{
    if ( m_output )  
    {  
        // Apply the function
        func(std::cout);
    }
    // But return the debug object
    return *this;
}

Implementation that handles both normal and wide streams:

#include <iostream>

#if defined(_DEBUG)
#define DEBUG_LOG_TEST_CONDITION        output
#define DEBUG_LOG_DEBUG_TEST_LEVEL(v)   (v) <= DebugCake::DebugLevel
#else
#define DEBUG_LOG_TEST_CONDITION        false
#define DEBUG_LOG_DEBUG_TEST_LEVEL(v)   false
#endif

template<typename C,typename T = std::char_traits<C> >
struct DInfo
{
    typedef std::basic_ostream<C,T>& (*StrFunc)(std::basic_ostream<C,T>&);
    static std::basic_ostream<C,T>& getDefault();
};

struct DebugCake
{
    static int DebugLevel;
};
template<typename C,typename T = std::char_traits<C> >
struct D
{

    D(int level)
        :stream(DInfo<C,T>::getDefault())
        ,output( DEBUG_LOG_DEBUG_TEST_LEVEL(level) )
    {}
    D(int level, std::basic_ostream<C,T>& s)
        :stream(s)
        ,output( DEBUG_LOG_DEBUG_TEST_LEVEL(level) )
    {}

    template<typename V>
    D& operator<<(V const& value)
    {
        // In release this is optimised away as it is always false
        if (DEBUG_LOG_TEST_CONDITION)
        {
            stream << value;
        }
        return *this;
    }

    D& operator<<(typename DInfo<C,T>::StrFunc func)
    {
        // In release this is optimised away as it is always false
        if (DEBUG_LOG_TEST_CONDITION)
        {
            func(stream);
        }
        return *this;
    }
    private:
       std::basic_ostream<C,T>&  stream;
       bool                      output;

};

template<>
std::ostream&  DInfo<char,std::char_traits<char>       >::getDefault()
{return std::cout; }

template<>
std::wostream& DInfo<wchar_t,std::char_traits<wchar_t> >::getDefault()
{return std::wcout; }

typedef D<char>    Debug;
typedef D<wchar_t> WDebug;
int DebugCake::DebugLevel = 4;


int main()
{
    Debug   debug(1);

    debug << "Plop" << std::endl;

    WDebug  debugWide(2);
    debugWide << L"WIDE" << std::endl;
}

You should write something like

operator<<(const T &t)

and use t instead of T (which is the type) inside this method.

The error is in here:

template<typename T>
Debug& operator<<( T )
{
    if ( m_output )
    {
        std::cout << T;
        return *this;
    }
    else
        return *this;
}

You cannot output a type, you have to output an object. Correct code:

template<typename T>
Debug& operator<<( T value)
{
    if ( m_output )
    {
        std::cout << value;
    }
    return *this;
}
template<typename T>
Debug& operator<<( T )

This signature is wrong; you've got a type for the argument to << , but not a name for it. You'll then want to use the variable name instead of T in std::cout << T;

The problem with using cout << T; has already been pointed out (you need to supply an object instance, whereas T is a type).

When you fix that, you'll run into at least one more problem: Debug(0) creates a temporary object. To pass that as a parameter to your nullstream operator<< , it'll need to take a nullstream const & instead of a nullstream & as its parameter.

...which will result in an l/rvalue problem. Just follow the std::cout pattern, if you want a global Debug output object. Otherwise, say:

Debug tDbg(tLevel);
tDbg << "Message" << std::endl;

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