The program we're going to present and annotate here:
Accepts a data source URI on the command line as the sole program argument.
Uses the data source factory to create a ring data source from wich ring items can be gotten.
Gets ring items from the data source until there are no more items (that can only happen for file data sources). As each Ring item is received, it is passsed off to a processing function. The processing function just indicates when physics events are received.
A secondary lesson from this program is the use of the
std::unique_ptr
template class of the C++ standard library to do storage management. We'll say more about that as we look at the code.
Let's look at the includes and declarations:
#include <CDataSource.h> #include <CDataSourceFactory.h> #include <CRingItem.h> #include <DataFormat.h> #include <Exception.h> #include <iostream> #include <cstdlib> #include <memory> #include <vector> #include <cstdint> static void processRingItem(CRingItem& item);
CRingItem
objects and since we're going to invoke methods on that object we also need to include the class definition for
CRingITem
CRingItem
class insulates us from the details of ring item formats. What we do need from this header are symbolic definitions for the ring item types.
CException
defined in the
Exception.h header is a base class for the exceptions NSCLDAQ throws. Several of the object methods we invoke can throw exceptions that are derived from this base class. We'll just catch CException&
so that we only need to know the base class interface.
We'll use a helper function usage
to output program usage and exit in error. Here's the definition and implementation of that function.
static void usage(std::ostream& o, const char* msg) { o << msg << std::endl; o << "Usage:\n"; o << " readrings uri\n"; o << " uri - the file: or tcp: URI that describes where data comes from\n"; std::exit(EXIT_FAILURE); }
Nothing really remarkable about this. Note, however how the exit
(3) function is encapsulated in the std namespace though the return codes it supports are not. This is because those return codes are preprocessor descriptions and those can't easily be embedded in a namespace.
The first part of the main program ensures we have a URI parameter so that we won't segfault trying to reference an argument that does not exist. We then use that argument to create a data source:
int main(int argc, char** argv) { if (argc != 2) { usage(std::cerr, "Not enough command line parameters"); } std::vector<std::uint16_t> sample; std::vector<std::uint16_t> exclude; CDataSource* pDataSource; try { pDataSource = CDataSourceFactory::makeSource(argv[1], sample, exclude); } catch (CException& e) { usage(std::cerr, "Failed to open ring source"); }
usage
function is called to output an error message and to exit.
sample
vector will hold the set of ring item types that can be sampled. Our example doesn't want to skip any ring item types so this vector will be empty.
exclude
vector will be a vector of the ring item types that can be filtered out. Once more, we don't want to miss any of the ring item types, so we leave the vector empty.
makeSource
static method analyzes the URI passed as its first parameter and uses that to create the appropriate data source. Note that there's a special case. If, instead of a URI, the data source name is
-, data will be taken from standard input allowing the
program to be a pipeline element.
The additional parameters specify the ring item types that can be sampled or filtered out entirely.
The creation of the data source is done inside a try block because it reports errors by throwing exceptions that are objects subclassed from
CException
.
CDataSourceFactory
::makeSource
throws an exception. In this example, it only uses usage
to output a generic message and exit. If you want a more informative error message, you can construct one
using the ReasonText
method of CException
as that method returns a string that describes the reason the exception was thrown.
Now that we've created a data source, let's look at the main loop of the program. The loop accepts data from the data source and passes it off to processRingItem
for processing.
CRingItem* pItem; while ((pItem = pDataSource->getItem() )) { std::unique_ptr<CRingItem> item(pItem); processRingItem(*item); } std::exit(EXIT_SUCCESS); }
CRingItem
objects from the data source. If the data source has no more items (can only happen for file data sources),
getItem
returns a null pointer and the while loop exits.
getItem
has to be deleted once you are done with it as it was dynamically allocated. The templated class
std::unique_ptr
is a pointer like object that manages a dynamically allocated pointer. Once destroyed (as it is automatically when it goes out of scope at the end of the block controlled by the while loop), it also destroys the pointer it manages.
The std::unique_ptr
is called pointer like because it implements
the unary operator*
and operator->
. These operators are implemented in such a way that a
std::unique_ptr
is virtually indistinguishable from the pointer it manages.
The name std::unique_ptr
means that only one of these should
manage any given pointer. To that end, the object does not support assignment or copy construction. If you need more relaxed semantics you can look at
std::shared_ptr
which uses reference counting to determine when to delete the managed pointer.
processRingItem
expects. So, for each ring item received from the data source, processRingItem
is used to do whatever processing is desired.
Finally the processsRingItem
function. This is really just a placeholder for whatever processing you'd like to perform. All we do in the example is determine if the ring item was a physics event (response to an experiment trigger) and, if so output that fact.
static void processRingItem(CRingItem& item) { if (item.type() == PHYSICS_EVENT) { std::cout << "Got a physics item\n"; } }
The constant PHYSICS_EVENT is defined in the header
DataFormat.h. It is one of several supported ring item types. The
type
method of CRingITem
returns the ring items's type.