Data Types Used in the C ++ Shared Library API

I am writing a generic C ++ library ( .so

) whose main function is to handle functions from images processed by OpenCV. However, the algorithm in this shared library is not specifically designed for vision - it can receive measurements from RADAR, LiDAR, etc.

Since I am doing library architecture, I try to keep OpenCV's dependency on it and use the more general Eigen3 matrix library instead.

My question is about the interface between application code and my library. I have a public method that will take a list of position and velocity measurements of each function from the application:

void add_measurements(std::vector< std::tuple<double, double> > pos, 
                      std::vector< std::tuple<double, double> > vel);

      

Is it better to leave the data structures on the API as primitive as possible, like the one above? Or should I force the application code to provide std::vector<Eigen::Vector2d>

for measurements?

Also, should I allow std::vector<cv::Point2f>

from the application code and then just convert to Eigen3 or something else inside? This seems to be the least useful since the library will still depend on OpenCV.

+3


source to share


2 answers


You can use generics to combine different data conventions without sacrificing performance.

The disadvantage is the possible higher learning curve for the interface.

First, instead of accepting vectors, you can accept iterators, which allow the user to provide data in other containers such as arrays and lists.

template<typename AccessType, typename PosIter, typename VelIter>
void add_measurements(PosIter p1, PosIter p2, VelIter v1, VelIter v2)
{
    // instantiate type to access coordinates
    AccessType access;

    // process elements

    // Internal representation
    std::vector<std::pair<double, double>> positions;

    for(; p1 != p2; ++p1)
        positions.emplace_back(access.x(*p1), access.y(*p1));

    std::vector<std::pair<double, double>> velocities;

    for(; v1 != v2; ++v1)
        positions.emplace_back(access.x(*v1), access.y(*v1));

    // do stuff with the data
}

      

Then, if they have a strange data type, they want to use like this:

struct WeirdPositionType
{
    double ra;
    double dec;
};

      

They can create a type to access their internal point coordinates:



// class that knows how to access the
// internal "x/y" style data
struct WeirdPositionTypeAccessor
{
    double x(WeirdPositionType const& ct) const { return ct.ra; }
    double y(WeirdPositionType const& ct) const { return ct.dec; }
};

      

Then it "connects" to the general function:

int main()
{
    // User weird and wonderful data format
    std::vector<WeirdPositionType> ps = {{1.0, 2.2}, {3.2, 4.7}};
    std::vector<WeirdPositionType> vs = {{0.2, 0.2}, {9.1, 3.2}};

    // Plugin the correct Access type to pull the data out of your weirt type
    add_measurements<WeirdPositionTypeAccessor>(ps.begin(), ps.end(), vs.begin(), vs.end());

    // ... etc
}

      

Of course, you can provide ready-made types Access

for generic point libraries such as OpenCv

:

struct OpenCvPointAccess
{
    double x(cv::Point2d const& p) const { return p.x; }
    double y(cv::Point2d const& p) const { return p.y; }
};

      

Then use can just use this:

add_measurements<OpenCvPointAccess>(ps.begin(), ps.end(), vs.begin(), vs.end());

      

+2


source


Remember that you can overload your functions to support many kinds of containers. If you think both will be helpful, then you don't have to choose between the two.

So the main considerations are the overhead associated with this and whether you want to add a dependency on Eigen. If a future version of the library will have a different implementation, you will not want to use the missing abstraction.

Another useful trick is to add a type alias, for example, inside a namespace:

using point2d = std::tuple<double, double>;

      

Which you can later change to:



using point2d = Eigen::vector2d;

      

Or:

using point2d = cv::Point2f;

      

You can make them more opaque by wrapping them in a structure. If you do this, future changes will break compatibility with the previous ABI, but not the API.

+1


source







All Articles