2017-05-06 44 views
3

我正在编写一个C++共享库(.so),它的主要功能是处理由OpenCV处理的图像的特征。但是,这个共享库中的算法并不是专门针对视觉的 - 它可以接收来自RADAR,LiDAR等的测量数据。C++共享库API中使用的数据类型

当我设计库时,我试图保留OpenCV依赖项,而不是使用更通用的Eigen3矩阵库。

我的问题是关于应用程序代码和我的库之间的接口。我有一个公共的方法,将接受来自应用程序的每个功能的位置和速度测量的列表:

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

是它最好留在API接口上的数据结构的原始越好,像上面?还是应该强制应用程序代码提供用于测量的std::vector<Eigen::Vector2d>

此外,我应该允许std::vector<cv::Point2f>从应用程序代码,然后转换为Eigen3或任何内部?由于图书馆仍然依赖于OpenCV,因此这似乎是最没有用的。

+0

一个模板化的实现(在你的库的头部)是否可以? (这实质上会使.so无用)并且你需要支持双精度和浮点数吗?你会成为你图书馆的主要用户吗?还是你可以强制图书馆用户的任意接口? – chtz

+0

IMO,使API“尽可能原始”意味着void add_measurements(const double * pos_data,const double * vel_data,size_t num_elements);'这将允许传递'Eigen :: Vector2d'的std :: vector'以及'std :: tuple '以及'cv :: Point2d'(由调用者进行一些编程工作,但在运行时没有拷贝开销) – chtz

回答

2

您可以使用泛型来桥接不同的数据约定而不牺牲性能。

不足之处在于界面可能有更高的学习曲线。

首先,而不是接受矢量你可以接受迭代,其允许用户在其他容器中提供数据,例如阵列列表

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 
} 

然后,如果他们有一个奇怪的数据类型,他们想用这样的:

struct WeirdPositionType 
{ 
    double ra; 
    double dec; 
}; 

他们可以创建一个类型访问其内部点坐标:

// 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; } 
}; 

然后它被'插入'到通用功能:

int main() 
{ 
    // User's 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 
} 

当然你也可以提供现成Access类型共同点库如OpenCv作者:

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

然后使用可以简单地使用:

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

其他缺点是:你的整个库必须存在于头文件,代码膨胀和不适当的模板参数给出混乱的错误未来版本的标准应该修复其中的一些(例如,让你说你的迭代器类型必须是迭代器).. – Davislor

+0

@Davislor必然存在权衡为*零开销*泛型,但我还没有发现*代码膨胀*是这样的问题,因为有些人可能认为你只是创建你使用的。它只是允许不同的程序使用不同的东西而不用重写。就错误消息而言,'static_assert'在这里已经有很多年了,并且提供了很容易理解的错误很长的路要走,所以我们不需要再为这个原因等待概念。 – Galik

1

记住,你可以重载你的功能支持多种容器。如果你认为两者都有用,你不需要在它们之间做出选择。

所以主要考虑的是每种方法的开销,以及是否要添加对Eigen的依赖。如果未来版本的库有不同的实现,则不想使用漏洞抽象。

另一个有用的技巧是添加一个类型别名,例如,在命名空间里:

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

,你以后可以更改为:

using point2d = Eigen::vector2d; 

或者:

using point2d = cv::Point2f; 

你可以通过将它们包装在一个结构中来使这些更加不透明。如果您这样做,将来的更改将会破坏与之前的ABI的兼容性,但不会影响API。