Re: DebugLog. Bad design or...??
Bo M?ller wrote:
I begin with a long explanation.
I want to make a logging class which is used like a ostream.
But in addition I need the log only put generate output for
messages which are >= the threshold of the instance e.g.
// ERROR > WARN > DEBUG
DebugLog log(WARN);
log.Report(DEBUG)<< "No output "<<std::endl;
log(ERROR) << "Output" << std::endl;
To achive this i declared Debuglog as:
class DebugLog: public std::ostream
{
public:
// Various Constructors omitted
std::ostream const &GetStream();
DebugLog &Report(const int &level);
bool const isActive();
private:
int m_threshold;
int m_level;
};
Deriving from std::ostream gave me all the inserters.
Not a good solution, in general. Many of the << operators are
NOT members of ostream, and all return an ostream&. So as soon
as you write something like log << "value is " << value, the
second value uses the base ostream for output, and not your
class.
But in order to switch the output on or off i defined the
functions and template below:
bool DebugLog::isActive() { return m_level >= m_threshold;}
std::ostream &DebugLog::GetStream()
{
return dynamic_cast<std::ostream&> (*this);
}
DebugLog &DebugLog::Report(const int &level)
{
m_level = level;
return (*this);
}
template< class T > DebugLog& operator<<( DebugLog& o , T const& obj
)
{
if( o.isActive() )
o.GetStream() << obj ;
return o ;
}
Which is fine, but there is no longer any reason for DebugLog to
derive from ostream. DebugLog contains an ostream; it isn't
one. (It's a bit subtle, because in some ways, the goal is that
a DebugLog could be used in all cases where an ostream could be
used. The problem here is that in order for the return types,
etc. to be correct, the resolution must take place at compile
time.)
The idea with the template was to forward all the insertions
to an std::ostream is the DebugLog was active, and discard all
if the DebugLog was inactive.
I did not get the idear by myself but from:
"http://groups.google.dk/group/comp.lang.c++.moderated/browse_thread/thread/
e61ad0c1feb3f0e7/7e6c3a1903784451?lnk=st&q=&rnum=3&hl=da#7e6c3a1903784451"
If the link doesn't work, the threads title was "Efficient
error-log filtering using streams" and the answer was given by
James Kanze on 11 feb. 1999:
2. More recently, using templates to define a logging stream which does
the test before converting, e.g.:
template< class T >
logstream&
operator<<( logstream& o , T const& obj )
{
if ( o.isActive() )
o.getStream() << obj ;
return o ;
}
It all works nicely until i pass :
log2.Report(x) << "Level " << x << std::endl << "#";
Then the output contains "\n#". Thus my template is NOT used,
but instead a template in std::ostream which returns an
std::ostream.
Is it possible to write a insertion operator for manipulators
which simply casts the manipulator away is the Debuglog is
inaktive?
I'm not sure of the exact reasons why it is necessary myself;
maybe someone who understands templates better than I do could
explain. But I've found that I need four separate template
functions for this:
template< typename T >
inline OutputStreamWrapper const&
operator<<(
OutputStreamWrapper const&
dest,
T const& obj )
{
std::ostream* stream = dest.stream() ;
if ( stream != NULL ) {
*stream << obj ;
}
return dest ;
}
inline OutputStreamWrapper const&
operator<<(
OutputStreamWrapper const&
dest,
char const* cString )
{
std::ostream* stream = dest.stream() ;
if ( stream != NULL ) {
*stream << cString ;
}
return dest ;
}
inline OutputStreamWrapper const&
operator<<(
OutputStreamWrapper const&
dest,
std::ios_base& (* manip)( std::ios_base& ) )
{
std::ostream* stream = dest.stream() ;
if ( stream != NULL ) {
*stream << manip ;
}
return dest ;
}
inline OutputStreamWrapper const&
operator<<(
OutputStreamWrapper const&
dest,
std::ostream& (* manip)( std::ostream& ) )
{
std::ostream* stream = dest.stream() ;
if ( stream != NULL ) {
*stream << manip ;
}
return dest ;
}
In the case of char const*, I'm pretty sure that the problem is
related to string literals, which causes the first template to
be instantiated with T == char const[] (and with a different
instantiation for each different length!), but I don't recall
ever figuring out exactly why that didn't work.
With regards to why the last two are necessary, I think (but I'm
not sure) that the problem is related to the fact that the
manipulators are, in fact, function templates. When you use
them with an ostream <<, type deduction on the left hand
parameter determines the instantiation type, and that type
permits successful type deduction on the right hand side. When
using an OutputStreamWrapper, however, the type of the ostream
is not visible for the compiler to use it in type deduction, and
it has no way of choosing which function is wanted among the
many possible instantiations of the template.
At any rate, the above set of functions seems to work with g++,
Sun CC and VC++.
Or is the design bad from the begining?
Well, I don't think so.
I have seen suggestions to set the failbit of the ostream, but
i think this is an ugly solution.
It's a quick and dirty solution, but I've used it in the past.
The problem here is that you'd have to constantly set and reset
it, each time, according to the results of your test.
In a multithreaded environment, there's another issue. In my
own code, the OutputStreamWrapper also manages a lock, but ONLY
if the output is active---no lock is acquired for an inactive
stream. This works because inactive streams never touch the
ostream object, only the pointer to it (which is immutable, and
so can safely be read without a lock). Playing around with the
fail bit means that you'd need to lock all instances.
Finally, my OutputStreamWrapper is designed to be copied (in
limited circomstances---I wouldn't recommend putting it in a
standard container), so you can return one by value. I've found
this useful in more than one context.
--
James Kanze GABI Software
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
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]