jeudi 25 août 2016

Safely passing around references to an abstract class in C++

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. 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 Devices, which in turn contain several IOs. A Device instance represents a concrete physical data-gathering device; its class (a descendant of the abstract Device 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 void Device::update().

  • Each IO 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 calling IO::get_value(), which returns a Datum.

  • Finally, the Node 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 reflected in the following diagram:

Diagram of class relations

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 stores its Devices and their IOs, the devices themselves store their own IOs as well, Data 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.

Aucun commentaire:

Enregistrer un commentaire