|
The UDS Collection
Features
back to main page
This section gives a brief description of the features UDS provides.
Sometimes the description is very briefly. Have a look at the class reference
and / or the header files.
Have also a look at the example source code in the test directory of
the source distribution.
Note:
Don't forget to read how to configure UDS in your
programs
Automatic search for memory leaks
When enabled, UDS keeps track of all memory allocations and deallocations.
When the program exits, it is checked whether all memory has been freed.
If not, a message like the following is printed to stderr. (see
test/example1.cc in the source distribution)
WARNING: 2 memory leak[s] detected
size address source function
-------------------------------------------------------------------------------
4 0x808f228 example1.cc:38 main
8 0x808f238 example1.cc:39 main
It is possible to set the number of expected memory leaks. This is
useful since some libraries or programs allocate a fixed amount of memory
which is never freed.
The following code snippet shows how to set the number of expected leaks
to 2
#include <uds/sentinel.hh>
...
uds::sentinel().Expect( 2 );
Note that it is possible to set several numbers of expected leaks.
You can also filter memory leaks by filename using the Sentinel::Ignore
method. The following snippet shows how to ignore all memory leaks which
are caused by functions defined in files which contain the substring 'include'
in its path.
#include <uds/sentinel.hh>
...
uds::sentinel().Ignore( "include" );
If you link against shared libraries you might want to call
Sentinel::IgnoreUnknown to ignore all memory leaks with unknown origin.
Logging of memory (de)allocations
Sometimes it is necessary to log all memory (de)allocations. This
option does exactly this. When enabled, messages like the following will
be printed to stderr. (see test/example2.cc in the source distribution)
function at address 0x804a7b0 allocates 4 bytes at address... 0x8058218
0x804a7c4 releases memory at 0x8058218... 4 bytes of memory allocated by 0x804a7b0 freed
Zombie objects
When memory is deallocated, the contents are usually not changed until
the memory is allocated again. Sometimes this shadows bugs when
objects are in use which seem to be intact, but have already been destroyed.
When this option is enabled, freed memory is overwritten. If the size
is dividable by the size of a pointer (there could be an object with
virtual functions) the memory is filled with pointers to a virtual function
table that contains only pointers to a function that throws a fatal exception.
That looks like the following. (see test/example3.cc in the source
distribution)
(6635) coredumping...
zombie object invoked:
virtual function call failed (fool: 0x8130838)
--- UDS backtrace (6635) ---
exception.cc:150 uds::Exception::dump()
exception.cc:68 uds::Exception::Exception(bool, std::string const&, uds::
DiagBase const&)
stdexcpt.hh:40 uds::zombie_object::zombie_object(std::string const&, uds
::DiagBase const&)
sentinel.cc:53 uds::throw_zombie(void*)
example3.cc:52 main
Backtraces at runtime
The Backtrace() function (declared in uds/btrace.hh) returns the backtrace
as a string. By default source files, function names, and line numbers
are printed instead of memory addresses (see test/example12.cc). If you want
just the memory addresses, pass false as the fist argument to Backtrace
(see the reference).
This function is very useful for debugging. test/example13.cc shows how to
use the Backtrace function in a signal handler.
Example output:
entered segfault handler
--- UDS backtrace (6684) ---
example13.cc:37 segv_handler(int)
sigaction.c:149 __libc_sigaction
example13.cc:52 foo()
example13.cc:60 main
Segmentation fault (core dumped)
This function is also used by the UDS exception classes (see below).
If the std_backtraces flag is set, a backtrace
is always generated when a UDS exception is thrown. You can retrieve it
with the Exception::Backtrace method. It is also part of the standard
error message (Exception::Message).
Since only the executable is checked, symbols from shared libraries
can not be resolved (static libs work fine).
Exception system for (almost) fatal errors
When there is a fatal error, you can eg print an error message and call
exit or abort. When you call abort, you have a core dump that can be
used to track down the problem, but no cleaning up is done since
global objects are not destroyed and no stack unwinding is done.
When you call exit at least global objects are destroyed, but you
have no core. When you throw an exception, all objects are destroyed, but you
have usually no backtrace, so you have to figure out where the exception was
thrown.
UDS provides exception classes that can dump a core when they are thrown by
calling fork and abort. So you have both a core dump and stack unwinding.
There are also a few exception class generation macros.
Note that you should place an instance of the uds::Init class in your main
function. If an instance was not created, it is assumed that an exception
might not be caught in case of a fatal error. Therefore an error message is
printed to stderr before the exception is thrown.
All UDS exception have a diagnose object that may contain additional
information (eg the value of the errno variable when a standard C library
function failed, or the exit status of a child process that terminates
unsuccessfully) and can give an error diagnose. The Diagnose() method is used
to retrieve this diagnose from an exception class, while the Message() method
produces a complete error message. It is also possible to get direct access
to the diagnose object with the Diag() method.
Since numeric error values are very common, all diagnose classes provide
the following virtual functions: HaveErrCode() returns true if the
diagnose object provides an error code. ErrCode() is used to retrieve the
error code. The meaning of this value depends on the diagnose class.
Starting with version 0.9.5, UDS exceptions can store a backtrace which can
be accessed via Exception::Backtrace. The backtrace is also added to the
error message generated by Exception::Message.
By default, the backtrace is only generated if a fatal error occurs (that
means if a core is dumped, see above). However, you can use the std_backtraces
flag to override this behavior. See Configuring UDS
for details.
Action, FinalAction, and VRemember
FinalAction and VRemember are useful classes, especially when you want to
write exception safe code. The constructor of FinalAction takes a function
/ function object that will be called when the FinalAction instance is
destroyed.
This is a convenient way to specify code that is always executed when the
function is left (no matter whether it returns normally or an exception is
thrown).
The VRemember class takes a reference to a variable and remembers the current
value. When the instance is destroyed, the original value is restored.
When a second argument is passed to the constructor, it is assigned to the
variable (after the old value was copied).
The Action template class is a wrapper for functions / function objects.
Its only template parameter is the return type of the function [object].
Action classes are useful when you 'store' function calls (like cleanup
handlers): my_atexit( const Action< void >& ) is much more flexible than
something like my_atexit( void ( * )( void* ), void* ) since you can use
a function object with any arguments, not just one void* argument.
Note that Action< void > means that the return type of function[object]s
doesn't matter - it doesn't have to be void.
AnyAction is a typedef for Action< void >.
Function objects that are more flexible than STL function objects
The STL defines a set of useful function objects. However they
support only unary and binary functions. Furthermore, you get into problems
when you want to use STL binder classes with references.
UDS provides function objects that can easily be created with calls to
uds::function and uds::function_ref. Binder or Adaptors are not required.
The first argument to uds::function is the function to be called. That can be
an ordinary function, or a member-function. The arguments to that function are
passed to uds::function as function objects. When the object returned by
uds::function is called, the argument function objects are called to
retrieve the arguments.
Have a look at the following example (test/example4.cc in the source
distribution).
void
print( ostream& out, const string& s )
{
out << s;
}
int
main()
{
uds::FinalAction x( uds::function( &print, uds::reference( cout ), uds::value( "bar\n" ) ));
print( cout, "foo" );
return 0;
}
This will print 'foobar'.
Note that the first argument, cout, is copied by reference while the second
argument is copied by value.
When x is destroyed, it calls the function object without an argument. The real
arguments of print are retrieved via calls to the function objects that were
passed to x.
However, it is possible to call UDS function objects with one argument. This
makes it possible to use them in STL algorithms like for_each(). In this case,
the argument function objects are called with the argument to the object
returned by uds::function. Some function objects (like those returned by
uds::value or uds::reference) will simply ignore the argument.
(see test/example5.cc in the source distribution).
typedef map< int, int* > Map;
Map m;
m[3] = new int();
m[6] = new int();
cout<<"deleting all map values...\n";
for_each( m.begin(), m.end(),
uds::function( Delete< int >,
uds::select2nd< Map::value_type >() ));
Threads, Mutexes, Semaphores
UDS provides several wrapper classes for Posix Threads. They include Thread
and Thread Attribute classes, Mutexes, Condition Variables, and Semaphores.
The classes provide methods for the pthread_* functions with a few (two)
extensions. Have a look at the reference or uds/thread.hh. The class
definitions are pretty straightforward (you should know Posix Threads though).
Thread instances can only be created on the heap. They maintain a
reference count and should be used with garbage collection smart-pointers
(see below). You don't have to delete
them manually; the reference count is decreased automatically when the thread
exits.
Have a look at the following example (test/example8.cc):
void*
thread_start()
{
cout << "new thread started\n";
sleep( 1 );
cout << "leaving new thread\n";
return 0;
}
void
cleanup( int a, double b )
{
cout << "cleanup " << a << " / " << b << endl;
}
int
main()
{
Thread& t = *new Thread( &thread_start );
t.PushCleanupHandler( function( &cleanup, value( 4 ), value( 3.68 ) ));
sleep( 2 );
cout<<"leaving main thread\n";
return 0;
}
The result is:
new thread started
leaving new thread
cleanup 4 / 3.68
leaving main thread
This shows extension #1: Thread::PushCleanupHandler and
Thread::PopCleanupHandler are much more flexible than their pthread_*
counterparts: They don't have to appear as pairs in the same function,
at the same level of block nesting. Cleanup handlers that are still on the
stack when the thread exits are executed. Since an Action instance is specified
as function to be called you are not limited to one void* argument.
When you construct the thread you pass an Action< void* > as start routine
(extension #2).
Noteworthy are also the MutexLock and CMutexLock classes that lock a mutex when
they are created, and unlock the mutex when they are destroyed. This is an easy
way to lock mutexes in an exception safe manner. CMutexLock registers a cleanup
handler to unlock the mutex. You can achieve the same with FinalActions, but
the MutexLock classes are easier to use.
Socket Classes
Socket classes for both connection and packet oriented protocols are
provided. Supported protocols are TCP (TCPSocket; streamsocket.hh), Unix Domain
sockets (stream; UnixSocket; streamsocket.hh), UDP (UDPSocket;
packetsocket.hh), and X.25 (X25_SWanpipe; swanpipe.hh; Sangoma wanpipe cards
under Linux only).
The base class for stream sockets, StreamSocket, is derived from iostream so
you can use all standard stream conversion operators, stream manipulators,
and UDS stream functions (like the BRead / BWrite and ReadLine families of
functions). There is also an additional stream manipulator, sockflags, which
can be used to change the flags which are passed to send() and recv().
The Listen(), Accept(), and Connect() methods are pretty much the same as
their system call counterparts. See the reference, test/example10.cc, and
test/example14.cc for more details and examples.
ProcStream class
The ProcStream class provides file stream operations for connections to child
processes. It was designed as replacement for popen(), but is more flexible.
Since a pair of anonymous unix-domain sockets is used instead of pipes both
read and write operations are supported. Furthermore it is possible to specify
the environment of the child process. As required by POSIX.2 for popen(),
Streams from other ProcStream instances that remain open in the parent process
are closed in the new child process. The use of this class is pretty
straightforward.
See the reference or uds/procstream.hh, and test/example11.cc for more
information.
Classes for reference counting that make it easy to
implement copy-on-write and garbage collection
UDS provides base class templates for reference counting, and smart pointers
for garbage-collection and copy-on-write. To make a class use reference
counting, derive it from one of the uds::RefCounter templates. The template
arguments are the type that is used to hold the reference count (size_t by
default) and a flag that indicates whether it is possible to mark objects as
unshareable. Unshareable objects are always copied instead of increasing
the reference counter.
For simple garbage collection use the uds::GC_Ptr template. Arguments are
the wrapped class and a flag that indicates whether those class defines
the method Clone(), which is used to copy the object instead of calling
the copy constructor directly. (see test/example6.cc)
// class that uses reference counting
class RefC : public uds::RefCounter<>
{
public:
RefC() { cout<<"constructor\n"; }
~RefC() { cout<<"destructor\n"; }
};
typedef uds::GC_Ptr< RefC > GC_Obj;
GC_Obj
new_object()
{
GC_Obj foo = new RefC();
// the reference count of the newly created object is now 1
GC_Obj bar = foo;
// the reference count is now 2
return foo;
}
int
main()
{
// This object must not be destroyed manually
// It will be destroyed when the reference count reaches 0.
GC_Obj x = new_object();
// the pointers in new_object() were destroyed; the reference
// count is now 1
return 0;
}
This prints
constructor
destructor
For copy-on-write (actually copy-on-maybe-write, see below), use uds::CoW_Ptr.
Template arguments are the same as GC_Ptr.
(see test/example7.cc)
// class that uses reference counting
class RefC : public uds::RefCounter<>
{
public:
RefC() { cout<<"constructor\n"; }
RefC( const RefC& ) { cout<<"copy constructor\n"; }
~RefC() { cout<<"destructor\n"; }
void
func() const {}
};
typedef uds::CoW_Ptr< RefC > CoW_Obj;
CoW_Obj
new_object()
{
return new RefC();
}
int
main()
{
CoW_Obj x = new_object();
const CoW_Obj y = x;
cout<<"calling member function through constant smart pointer\n";
y->func();
cout<<"calling member function through non-constant smart pointer\n";
x->func();
return 0;
}
The output of the program is
constructor
calling member function through constant smart pointer
calling member function through non-constant smart pointer
copy constructor
destructor
destructor
Note that if you call a const method through a non-const CoW_Ptr, the wrapped
object will be copied. Therefore you should use const CoW_Ptrs whenever
possible.
Simple random number generators
UDS provides a few simple random number generator classes. However, they are
not much more than rand() replacements. While they are good enough for most
games and simple apps, you should not use them in complex mathematical
programs.
- RandInt is used to generate random numbers between 0 and 2^31, and floats
between 0 and 1.
- URand generates random numbers (ints and floats) between 0 and a maximum
- ERand generates exponentially distributed random numbers between 0 and 2^31
They are simple to use; have a look at the class reference or uds/random.hh.
Several "convenience functions" to create temporary
file names; open files, fork, wait etc. and throw an exception if something
goes wrong
UDS provides a few convenience functions, that perform system operations and
throw an UDS exception if the function fails. The exception contains error
descriptions according to the value of the errno variable.
For a list and documentation of the convenience functions, have a look at
the class reference or uds/sys_util.hh.
Logging class
UDS provides a logging class which can send log messages to logfiles
and the system logger. You can set different log levels for both
types of output. If the priority of the message to be sent is less
than the log level, it gets logged.
When specifying priorities or log levels, you can use the constants
defined by syslog ranging from LOG_EMERG (0) to LOG_DEBUG (7).
However, values up to 255 are supported.
See the class reference and example16.cc for more information.
Alternative file stream class
The FileStream class works basically the same way as standard fstreams
do, with a few extensions:
- Additional flags:
- excl
- exclusive file creation (equivalent to O_CREAT)
- create
- create file if it does not exist. Does not require ios::trunc
- independent input / output file positions
- direct access to the file descriptor (to lock the file etc.)
See the reference and example15.cc for more information.
|