Skip to content

Resolve "feat(core): add generic container classes"

Didier WECKMANN requested to merge 885-feat-core-add-generic-container-classes into dev

Description

Implements a generic templated data container class, aka IContainer, that allows code factorization from all containers. STL API from the corresponding STL container classes is exposed and the inherited containers, such Composite and Vector can be seen as a real std::vector and std::map from outside (which they indeed are !), while keeping to be data objects, that can be used in XML, that can be serialized, etc..

Since it is a part of #862 (closed), some containers (ActivitySet, CameraSet, SeriesSet) have been also implemented. Although not used yet, you can still take a look at them, as CameraSet uses a more complex std::vector<std::pair<Camera::sptr, Matrix4::sptr>> while ActivitySet and SeriesSet use a Boost Multi-index, which allows to define a set which is sequenced like a vector.

Usage

The best open Composite or Vector and IContainerTest code, but basically, all you have to do if you need a specific data object container class, is to inherits from sight::data::IContainer<XXX>, with XXX being your STL container type (or boost STL compatible container, like Multi-index !). As an example:

#include "data/IContainer.hpp"

class DATA_CLASS_API Vector final : public IContainer<std::vector<Object::sptr> >
{
...
};

You can then use it like a real std::vectorObject::sptr:

auto vector = sight::data::Vector::New();

// 99.9% of STL API is available
vector.reserve(2)
vector.push_back(sight::data::Integer::New(1));
vector.push_back(vector.front())
vector[1] = sight::data::Integer::New(2)
...

// Initializer list / assignment operators 
vector = {sight::data::Integer::New(1), sight::data::Integer::New(2), sight::data::Integer::New(3)};

// iterators are supported
for(const auto& element ; *vector)
{
    std::cout << element->getValue() << std::endl;
}

Notifier

There is also a generic Notifier which replace various "helpers", that were used to send signals when adding / removing objects in the container. It uses RAII mechanism to send the right signals when it is deleted.

To use them, simply call the generic get_notifier() function form a container to get a Notifier instance, and perform operation on the container. The notifier will take a snapshot of the content and compare it with the current container upon destruction. Signals will be fired if elements have been added or removed or changed (in case of a map like container). Short example:

auto composite = sight::data::Composite::New();

{
    auto notifier = composite->get_notifier();

    // Now modification to `composite` will be notified to whatever is connected to `composite` signals
    composite->insert({"beast", sight::data::Integer::New(666)});
    ...

    // Signals are sent when notifier is destroyed, like outside this scope...
}

Advanced usage

Writing specific code for specific container

Sometimes, it is useful to know if an object is a container and if yes, from which kind it is. Some template matching functions have been added to core/tools/compare.hpp:

#include <core/tools/compare.hpp>

template<typename T>
void my_function(const T& truc)
{
    ...
    if constexpr(core::tools::is_map_like<T>::value)
    {
        truc.insert({"maman", value});
    }
    else if constexpr(core::tools::is_container<T>::value)
    {
        truc.insert(value);
    }
    else
    {
        ....
    }
}

Generic container without inheriting from IContainer

If you don't want the IContainer<XXX> inheritance because you don't want to be a sight::data::Object, but still want to act like a XXX, you could inherit directly from ContainerWrapper<XXX>:

class MySimpleContainer : public ContainerWrapper<std::set<std::string>>
{
    ...
};

MySimpleContainer a;

a.insert("Le petit chaton bleu est très malade.");

...

See #885 (closed) for some details

Closes #885 (closed)

How to test it?

Look the code and launch unit tests (especially datatest)

Edited by Didier WECKMANN

Merge request reports