FlashCactus FlashCactus - 3 months ago 11
C++ Question

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
    Device
    s, which in turn contain several
    IO
    s. 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 (a bit loosely) 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.

Answer

Use std::unique_ptr for unique ownership and std::shared_ptr for shared ownership. This makes passing pointers much safer.

std::unique_ptr cannot be copied, and the pointed-to object is automatically deallocated when the unique_ptr is destroyed (e.g. goes out of scope). Conversely, the std::shared_ptr CAN be copied, and the pointed-to object is only deallocated when all copies of the shared_ptr are destroyed.

If you instrument you code with the above tools and also use std::make_unique (C++14) and std::make_shared (C++11), largely free of manual new and delete and avoid a lot of memory related issues.

Comments