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
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: 1 memory leak(s) detected
function at 0x804a7b0 did not release 4 bytes of memory at address 0x8058218
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
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)
(12582) coredumping...
zombie object invoked:
virtual function call failed (fool: 0x80c89c0)
--- UDS backtrace (12582) ---
exception.cc:150 ??
exception.cc:70 ??
stdexcpt.hh:40 ??
sentinel.cc:49 uds::throw_zombie(void *)
example3.cc:50 main
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:
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).entered segfault handler
--- UDS backtrace (12585) ---
example13.cc:36 segv_handler(int)
tinfo2.cc:300 signed char type_info function
example13.cc:51 foo(void)
example13.cc:59 main
Segmentation fault (core dumped)
Since only the executable is checked, symbols from shared libraries
can not be resolved (static libs work fine).
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 behaviour. See Configuring UDS
for details.
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 specifiy 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 >.
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.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 >() ));
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.
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.
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.
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.// 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.
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.
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.