g2o构造求解器的陷阱——关于unique智能指针的禁止复制语义

在读高博关于g2o的一篇讲解博客https://www.cnblogs.com/gaoxiang12/p/5304272.html的时候,我打算顺便跑下作者的示例代码g2o_ba_example,期间遇到了一些问题。

代码github地址:GitHub - gaoxiang12/g2o_ba_example: An easy example of doing bundle adjustment within two images using g2o.

本文顺便作为日志记录学习过程,先简单提一下编译遇到的

1.opencv版本问题

这是由于新旧版本特征提取操作的差异引起的,作者当时用的是opencv2.4.*,其提取特征点是用的cv::ORB对象的构造函数:

cv::ORB orb;

vector<cv::KeyPoint> kp1, kp2;

cv::Mat desp1, desp2;

orb( img1, cv::Mat(), kp1, desp1 );

orb( img2, cv::Mat(), kp2, desp2 );

如果cmake时链接到opencv3以上版本,会有如下报错:

error: no match for call to ‘(cv::ORB) (const cv::Mat&, cv::Mat, std::vectorcv::KeyPoint&, cv::Mat&)’
orb( img1, cv::Mat(), kp1, desp1 );

可以对比以下opencv3提取特征点的操作:

std::vector<KeyPoint> keypoints_1, keypoints_2;

Mat descriptors_1, descriptors_2;

Ptr<FeatureDetector> detector = ORB::create();

Ptr<DescriptorExtractor> descriptor = ORB::create();

...

detector->detect(img_1, keypoints_1);

detector->detect(img_2, keypoints_2);

descriptor->compute(img_1, keypoints_1, descriptors_1);

descriptor->compute(img_2, keypoints_2, descriptors_2);

所以解决办法是要么将源代码改为后一种提取方式,要么在系统中另装opencv2,并在cmakelist中修改链接版本(我另装了opencv2.4.13):

find_package( OpenCV 2.4 REQUIRED )

接下来是本文的重点,即

2.在g2o中使用unique_ptr构造求解器的陷阱

同样是在编译时报错:

error: no matching function for call to ‘g2o::BlockSolver<g2o::BlockSolverTraits<6, 3> >::BlockSolver(g2o::BlockSolver<g2o::BlockSolverTraits<6, 3> >::LinearSolverType*&)’

...

error: no matching function for call to ‘g2o::OptimizationAlgorithmLevenberg::OptimizationAlgorithmLevenberg(g2o::BlockSolver_6_3*&)’
   70 | onAlgorithmLevenberg* algorithm = new g2o::OptimizationAlgorithmLevenberg( block_solver );

出问题的相关代码行(66-70):

g2o::BlockSolver_6_3::LinearSolverType* linearSolver = new g2o::LinearSolverCholmod<g2o::BlockSolver_6_3::PoseMatrixType> ();

// 6*3 的参数

g2o::BlockSolver_6_3* block_solver = new g2o::BlockSolver_6_3( linearSolver );

// L-M 下降

g2o::OptimizationAlgorithmLevenberg* algorithm = new g2o::OptimizationAlgorithmLevenberg( block_solver );

查看相应构造函数的定义:

BlockSolver(std::unique_ptr<LinearSolverType> linearSolver);

explicit OptimizationAlgorithmLevenberg(std::unique_ptr<Solver> solver);

注意到其参数均为std::unique_ptr(因为我之前已经安装了g2o库,这是来自最新的g2o版本源码定义),很明显作者在写g2o_ba_example这个demo的时候,当时版本的g2o中关于这几个类的构造函数参数不是unique_ptr(因为是c++11之后发布)。

解决办法是要么使用g2o的更早版本:Release 20170730_git · RainerKuemmerle/g2o · GitHub

要么改动代码,将对应的线性方程求解器、BlockSolver对象都设置为unique_ptr。由于感觉只需改动三行代码,于是我将linearSolver和block_solver改成了unique_ptr对象:

    // g2o::BlockSolver_6_3::LinearSolverType* linearSolver = new  g2o::LinearSolverCholmod<g2o::BlockSolver_6_3::PoseMatrixType> ();
    typedef g2o::LinearSolverCholmod<g2o::BlockSolver_6_3::PoseMatrixType> LinearSolverType;
    auto linearSolver = g2o::make_unique<LinearSolverType>();
    // 6*3 的参数
    // g2o::BlockSolver_6_3* block_solver = new g2o::BlockSolver_6_3( linearSolver );
    auto block_solver = g2o::make_unique<g2o::BlockSolver_6_3>(linearSolver);
    // L-M 下降 
    // g2o::OptimizationAlgorithmLevenberg* algorithm = new g2o::OptimizationAlgorithmLevenberg( block_solver );
    g2o::OptimizationAlgorithmLevenberg* algorithm = new g2o::OptimizationAlgorithmLevenberg(block_solver);

编译仍然报错找不到匹配的构造函数:

error: no matching function for call to ‘g2o::OptimizationAlgorithmLevenberg::OptimizationAlgorithmLevenberg(std::unique_ptr<g2o::BlockSolver<g2o::BlockSolverTraits<6, 3> >, std::default_delete<g2o::BlockSolver<g2o::BlockSolverTraits<6, 3> > > >&)’
   74 | tionAlgorithmLevenberg* algorithm = new g2o::OptimizationAlgorithmLevenberg(block_solver);

...

/usr/local/include/g2o/stuff/misc.h:54:29: error: no matching function for call to ‘g2o::BlockSolver<g2o::BlockSolverTraits<6, 3> >::BlockSolver(std::unique_ptr<g2o::LinearSolverCholmod<Eigen::Matrix<double, 6, 6, 0> >, std::default_delete<g2o::LinearSolverCholmod<Eigen::Matrix<double, 6, 6, 0> > > >&)’
   54 |   return std::unique_ptr<T>(new T(std::forward<ArgTs>(args)...));

这下给我彻底整懵了,一度以为是中间的模板类型BlockSolverTraits<6, 3>被typedef等一系列别名行为搞出了问题......总之就是琢磨折腾了非常久,最后才弄明白是因为unique_ptr的特性引起的。

unique_ptr禁止拷贝和赋值,查看其定义,在类定义的最下方有两行代码:

// Disable copy from lvalue.
unique_ptr(const unique_ptr&) = delete;
unique_ptr& operator=(const unique_ptr&) = delete;

“=delete”即表明将拷贝构造和赋值操作禁用了。在我改动的代码中,先将LinearSolverType类型的unique_ptr指针初始化给了linearSolver对象,再将linearSolver这个对象作为参数传给BlockSolver_6_3的构造函数(实质为BlockSolver基类的构造函数);后续同样先初始化block_solver的unique_ptr指针,再传给OptimizationAlgorithmLevenberg的构造函数。

这是不允许的。因为这样行为的本质就是将一个unique_ptr作为参数传给一个新unique_ptr的构造函数,即拷贝构造。

unique_ptr 独占所指向的对象,即在某一时刻,只能有一个unique_ptr指向特定的对象。而拷贝构造以unique_ptr作为参数,会首先需要将其赋值一遍,这违反了设计规则,无法做到。 

解决办法:要么使用std::move()移动构造(将std::unique_ptr对象持有的堆内存转移给另外一个对象,其不再持有堆内存的引用,变成一个空的智能指针对象),要么直接嵌套unique_ptr的构造函数,将其指向的堆内存的所有权在一开始就交给同一个对象。

使用std::move()移动构造如下:

    typedef g2o::LinearSolverCholmod<g2o::BlockSolver_6_3::PoseMatrixType> LinearSolverType;
    auto linearSolver = g2o::make_unique<LinearSolverType>();
    auto block_solver = g2o::make_unique<g2o::BlockSolver_6_3>(std::move(linearSolver));
    g2o::OptimizationAlgorithmLevenberg* algorithm = new g2o::OptimizationAlgorithmLevenberg(std::move(block_solver));    

直接使用构造函数嵌套如下(可读性不太友好):

    typedef g2o::LinearSolverCholmod<g2o::BlockSolver_6_3::PoseMatrixType> LinearSolverType;
    g2o::OptimizationAlgorithmLevenberg* algorithm = new g2o::OptimizationAlgorithmLevenberg(g2o::make_unique<g2o::BlockSolver_6_3>(g2o::make_unique<LinearSolverType>()));   

这两种方法均可以使编译通过。

参考了这几篇介绍拷贝构造和unique_ptr的文章视频:

C++基础——拷贝构造函数_橙予清的zzz~的博客-CSDN博客

探究C++11智能指针之std::unique_ptr - 知乎

unique_ptr的使用和陷阱_River_Lethe的博客-CSDN博客

彻底搞懂之C++智能指针-腾讯云开发者社区-腾讯云

C++智能指针Unique_ptr使用_哔哩哔哩_bilibili

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值