I disagree, if we understand that the goal is to investigate
what a particular implementation actually does, and not
something to be used in production code.
The only question I have to pose here is the use of size_t. In
practice, it's probably an adequate choice for almost any
implementation, but formally, anything but a character type
results in undefined behavior.
I have the following in my library, expressedly for such cases:
// Dump:
// =====
//
//! This is a simple class which can be used to generate a hex
//! dump of any object. (It generates the hex dump all on a
//! single line, as a sequence of two byte hex values,
separated
//! by spaces.) In almost all cases, the actual instance of
this
//! class will be a temporary, generated as the return value
of
//! the function template dump(), so that template type
deduction
//! can be used.
//!
//! \warning
//! This class does <strong>not</strong> save a copy of
the
//! object, but only a reference to it. This means that
it
//! will contain a dangling reference if the object ceases
to
//! exist before the instance of the class. On the other
//! hand, it also means that it is possible to dump the
memory
//! of objects which cannot be copied, a signaling NaN,
for
//! example. It is, however strong recommended that only
//! temporary instances of this class be used (and that no
//! instance be bound to a reference, other than as a
function
//! argument); the rules concerning the lifetime of
temporarys
//! make this always safe.
//
---------------------------------------------------------------------------
template< typename T >
class Dump : Gabi::Util::IOStreamOperators< Dump< T > >
{
public:
explicit Dump( T const& obj ) ;
void print( std:
stream& dest ) const ;
//!@cond implementation
private:
unsigned char const*myObj ;
//!@endcond
} ;
//! Allows the use of template type deduction, i.e.:
//! \code
//! std::cout << dump( someObject ) ;
//! \endcode
//! rather than
//! \code
//! std::cout << Dump< ObjectType >( someObject ) ;
//! \endcode
//!
//! \param obj
//! The object for which an instance of Dump<T> is
desired.
//!
//! \return
//! The instance of Dump<T>.
//
---------------------------------------------------------------------------
template< typename T >
inline Dump< T >
dump(
T const& obj )
{
return Dump< T >( obj ) ;
}
template< typename T >
Dump< T >:
ump(
T const& obj )
: myObj( reinterpret_cast< unsigned char const* >( &obj ) )
{
}
template< typename T >
void
Dump< T >:
rint(
std:
stream& dest ) const
{
IOSave saver( dest ) ;
dest.fill( '0' ) ;
dest.setf( std::ios::hex, std::ios::basefield ) ;
char const* baseStr = "" ;
if ( (dest.flags() & std::ios::showbase) != 0 ) {
baseStr = "0x" ;
dest.unsetf( std::ios::showbase ) ;
}
unsigned char const* const
end = myObj + sizeof( T ) ;
for ( unsigned char const* p = myObj ; p != end ; ++ p ) {
if ( p != myObj ) {
dest << ' ' ;
}
dest << baseStr << std::setw( 2 ) << (unsigned int)
( *p ) ;
}
}
(Note that the class template IOStreamOperators generates the <<
and >> operators automatically, and IOSave, of course, saves the
various formatting information, and restores it in its
destructor.)
Again, I don't think I've ever used it in production code, but
it's often useful for checking assumptions about the
implementation (big endian or little, are doubles IEEE, etc.).
[...]
BTW, should I have preferred a C-style cast such as
"(size_t*)pv" instead of reinterpret_cast, in such a cheat?
Absolutely not. The purpose of reinterpret_cast is to allow
such cheats. And to make them visible as such.