The Ten-Minute Guide to NMSTL

by Jon Salz


WTF, the G++ Output Postprocessor

Before we even get started with NMSTL... working with template-heavy code like NMSTL and STL can involve some really weird-looking compiler errors. For instance, this is g++'s way of complaining politely that you forgot to define an operator << for your class:
../nmstl/thread: In copy constructor `nmstl::guard<Lock>::guard(const
   nmstl::guard<Lock>&) [with Lock = nmstl::mutex]':
../nmstl/seda:277:   instantiated from here
../nmstl/thread:302: no match for `std::ostream& <<
   nmstl::guard<nmstl::mutex>::my_thread&' operator
/home/jsalz/gcc3/include/c++/3.2/bits/ostream.tcc:55: candidates are:
   std::basic_ostream<_CharT, _Traits>& std::basic_ostream<_CharT,
   _Traits>::operator<<(std::basic_ostream<_CharT,
   _Traits>&(*)(std::basic_ostream<_CharT, _Traits>&)) [with _CharT =
   char, _Traits = std::char_traits<char>]
Oh, by the way, the last five lines are repeated about 25 times.

Reading such error messages may leave you scratching your head, or perhaps standing on your chair yelling such phrases as "WTF?!!" (What's The Faux-pas?)

Anyone who has experienced this problem, which is to say anyone who uses STL, may want to take a look at "WTF", a little Perl hack which makes these error messages a lot more easily decipherable and gives you some context, like:

../nmstl/thread:302:no match for `ostream& << my_thread&' operator
300>
301>         my_thread t;
302>         cout << t;
303>     }
304>     guard& operator = (const guard& g) {
N.B.: You probably need to use an NMSTL_TO_STRING(my_thread);
macro immediately following your class declaration for my_thread.
Much nicer eh? You can find wtf in the bin directory of the distribution - I find it useful for nearly any C or C++ development.

NMSTL Base functionality

Some of NMSTL's functionality is included whenever you include any NMSTL file.

Debugging: <nmstl/debug>

<nmstl/debug> contains a simple debugging framework. There are six debug levels, and six corresponding macros to generate debug messages:
DEBUG << "foo is " << foo
INFO << "Accepted connection from " << addr;
WARN << "...";
ERROR << "...";
FATAL << "Assertion failed";    // Also kills program
COREDUMP << "Assertion failed"; // Kills program, leaving core dump
You can pass any sorts of arguments to these macros, in the typical iostreams fashion (although they're not technically iostreams). Each argument will be stringified, if necessary, using to_string.

To determine which messages are printed, the debugging code reads the DEBUG environment variable. Warnings, errors, fatal errors, and core dumps are always displayed. Info messages are displayed only if the DEBUG environment variable is set to 1 or higher; debug messages are displayed only if the DEBUG environment variables is set to 2 or higher.

Time: <nmstl/ntime>

The ntime class encapsulates a point in time, or a duration in time. (As with time_t, a point in time is represented as a number of seconds such the UNIX epoch, 1 January 1970.) ntimes have microsecond precision. You can create time objects with the following static methods: ntime objects can also be added and subtracted to each other, and multiplied and divided by scalars.

Smart pointers: <nmstl/ptr>

What C++ class library would be complete without yet another smart pointer class? For any type T, a ptr<T> is a reference-counted pointer of type T. An object is automatically deleted once the last ptr to it is destructed.

ptrs are threadsafe, by the way.

Input and Output: <nmstl/io>

In C, one usually refers to a data buffer by a memory-address/length tuple, as in read or write:
ssize_t read(int fd, void *mem, size_t length);
ssize_t read(int fd, const void *mem, size_t length);
NMSTL provides abstractions over the memory-address/length tuple: These abstractions are very handy when reading and writing data, usually using the iohandle class defined by NMSTL. An iohandle is a reference-counted file descriptor; the descriptor is automatically closed when the last iohandle to it is deleted.

For instance, here's how to read data from an iohandle:

void printbuf(constbuf buf) {
    cout << "I just read " << buf.length() << " bytes" << endl;
}

void read_and_print(iohandle ioh) {
    iohandle ioh = ...;
    dynbuf buf(2048); // 2048-byte buffer
    ioh.read(buf);
    printbuf(buf);

    // buffer is automatically deallocated when it goes out of scope
}
Note that printbuf takes a constbuf as input - that's because it doesn't need to change the data in the buffer. If it needed to change the buffer, it could take a databuf as input. If it needed to keep a copy of the buffer, it could take a dynbuf as input. A dynbuf can be implicitly casted to a databuf, and a databuf can be implicitly casted to a constbuf, just as a void * can be implicitly casted to a const void *.

Status Codes

Many of the I/O routines return status objects. A status is simply a boolean value (true for "good" or false for "bad") plus an optional detail string describing the reason for failure. A status can be cast to a boolean, so you can say, for example,
if (!my_ioh.stat()) {
    cout << "Error: " << my_ioh.stat();
}

Networking: <nmstl/net>

An address is a network address; an inet_address is an IP address (optionally with a port). A socket is an iohandle with extra socket-type features. A tcpsocket is a socket for, you guessed it, TCP connections; to establish a TCP connection, use construct it as
  1. tcpsocket(address_to_connect_to) for a blocking connect;
  2. tcpsocket(address_to_connect_to, socket::nonblocking) for a nonblocking connect; or
  3. tcpsocket(address_to_connect_to, socket::acceptor) to listen for connections on a particular address/port.

Asynchronous I/O: <nmstl/ioevent> etc.

Now we're getting to the good stuff. io_event_loop is an asynchronous I/O event loop. For asynchronous-callback-driven servers, you'll usually have one of these, and a slew of io_handler objects to handle each individual event source (e.g., the network).

An io_handler handles events for a single file descriptor. To write such a handler, you'd subclass io_handler; use set_ioh to specify the file descriptor; call want_read and/or want_write to specify which events you're interested in; and override ravail and wavail to handle those events.

You'll find several useful networking handlers in <nmstl/netioevent>.

Terminal I/O: <nmstl/terminal>

Many clients and servers can benefit from a command-line interface, so NMSTL provides the term_handler, an asynchronous command-line handler which uses GNU Readline and GNU History to provide editing, key rebinding, history, and command completion (not to mention automatic help functions). Much prettier than cin >> foo (and safe to use in an asynchronous server!).

Serialization: <nmstl/serial>

NMSTL provides a serialization framework which can be used to turn C++ structs into network data, and vice versa. Here's how it works: You can define serializers for your own classes, of course; check out the documentation for the oserial class for details.

Callbacks: <nmstl/callback>

It's often very useful to be able to pass a function or method pointer as a parameter, to be invoked later with certain arguments (and maybe returning a value) when some condition occurs. Function and method pointers have limited functionality, though - one can't pass state around with a simple pointer. A callback encapsulates a function or method, plus some state to be passed to that function or method when it is actually invoked.

Threading: <nmstl/thread>

If you don't want to do everything asynchronously, you'll probably need threads. We provide the typical thread class, as well as mutex and a condition classes. You can use the locking macro to acquire and release a mutex automatically inside a particular scope.

Another useful abstraction in servers is a threaded queue (tqueue, defined in <nmstl/tqueue>), allowing writer threads to post items to a queue and readers to wait for items to consume.

MixedCase API: <NMSTL/*>

Prefer class Thread to class thread and class IOEventLoop to class io_event_loop? The NMSTL directory provides all the NMSTL APIs with MixedCase names instead of stl_like_names, so you can use whichever happens to match your coding conventions. To use it, just use
#include <NMSTL/ioevent>
instead of
#include <nmstl/ioevent>

NMSTL In Action

Take a look at chatclient.cc and chatserver.cc in the examples directory to see how it all fits together!
jsalz-nmsrlm@mail.jsalz.net