I am developing an application for a data-collection controller. It has to
interface with lots of different other devices, which in turn might provide
different types and amounts of data. To this end the following hierarchy was
devised (it's a bit lengthy but bear with me):
- The basic unit of information is the Datum.
itself is an abstract
class, the descendants of which represent different types of [physical]
quantity: temperature, pressure, energy, power, relay state, etc. Each Datum
instance represents a single reading (at some moment in time).
- Data are collected by
s, which in turn contain several
s. A Device
instance represents a concrete physical data-gathering device; its class (a
descendant of the abstract
class) represents the model of device and
contains all the model-specific code necessary to interface with it and
extract readings from it. This is done by calling the virtual function
instance represents a variable which a device collects. For example,
if the device is a multi-channel temperature monitor, then an IO represents a
single sensor connected to the device. The IO can be queried for a value by
, which returns a
- Finally, the
class keeps a list of all devices attached to the
controller, another list of all IOs in those devices, and provides methods for
polling all devices at once, individual devices, individual IOs, etc.
These relationships are (a bit loosely) reflected in the following diagram:
Now, for the problem itself:
In all of this, a lot of instances of descendants of abstract
classes must be passed around and stored all the time: the Node
s and their IO
s, the device
s themselves store their own IO
get created and returned and passed around and destroyed, the
device and IO list gets updated in place, etc. But it is unclear how to
implement all this passing around:
- passing instances of abstract classes by value is obviously out of the question, and
even it they were not abstract, it might result in object slicing.
- passing them by reference works for arguments of a function, but what about
creating and returning Datum instances? they get created as local variables in the
IO::get_value method, and so are destroyed when it returns.
Additionally, it is not possible to store references, e.g. in an std::map.
- finally, passing pointers is dangerous. In order to return something as a
pointer one must allocate memory in the method, and then it is the caller's
business to free the memory after the returned value is no longer used. In
addition to being inconvenient, it presents a danger of getting a
memory leak, doing a double free, or having some other part of the program
dereference a now-empty pointer that has been stored there beforehand.
So I am at a loss as to how one might implement a robust system of exchanging
objects like this, with more-or-less foolproof ways of ensuring that the objects
behave as a variable passed by value (stay in existence as long as they are
needed, but not longer) while retaining the duck-typing provided by inheritance
of a common interface.