从视觉SLAM方案中理解C++的语法条款(一)

 前言:

       本文以开源ORB_SLAM2为例,分析Scott Meyers大佬的Effective C++,同时还用到了视觉slam十四讲高博的开源代码。使用这些素材其实是因为我最近也在学习SLAM,发现自己对SLAM代码中的许多语法不明所以。希望这个系列能给大家带来收获,我们一起进步!

资料链接:

  • 链接: https://pan.baidu.com/s/16eN1vPq3Et1LEpXWlwColQ?pwd=hzfn 提取码: hzfn 复制这段内容后打开百度网盘手机App,操作更方便哦
  • 内含:ORB_SLAM2代码,kitti数据集 2个G, Effective C++ PDF

目录

 条款01:视C++为一个语言联邦

   <1> 四大联邦 - 四大次语言(只是建立次语言概念,不细讲知识点)

  <2> 四大联邦守则

🥝 条款02:尽量以const, enum, inline 替换 #define

宁可以编译器代替与处理器:

🌈 条款03:尽可能用const

🍊条款04: 确定对象使用前已被初始化

条款05 (大部分教程里都有,故略写)

条款06 :明确地拒绝复制(这不是原标题,我自己总结的)

条款07:多态基类声明virtual析构函数 

条款08 :别让异常逃离析构函数

🍉条款09:绝不在构造和析构函数内调用虚函数

 条款10:令operator= 返回一个reference to *this (只是代码规范


 条款01:视C++为一个语言联邦

   <1> 四大联邦 - 四大次语言(只是建立次语言概念,不细讲知识点)

  1. C                                      C语言

  2. Object-Oriented  C++     面向对象: 封装、继承、多态

  3. Template C++                  模板 范性编程 generic programming

  4. STL                                  容器、迭代器、算法

  <2> 四大联邦守则

  • C 内置类型                                                pass-by-value                         传递值

  • Object-Oriented  C++ 用户自定义类型    pass-by-reference-const       传递常量引用,通过引用避免开辟新的内存,因为自定义类型(结构体、类等)体积较大

  • STL                                                            pass-by-value                         但我看到string还是pass-by-reference-const

/***以我最近学习的视觉SLAM工程中的相机类为例***/
// 注意力放在Camera类的构造函数上


#ifndef CAMERA_H
#define CAMERA_H

#include <opencv2/core/core.hpp>

#include <sophus/se3.hpp>

class Camera{
public:
    double fx_ = 0, fy_ = 0, cx_ = 0, cy_ = 0;
    Sophus::SE3d pose_;
    Camera(double fx, double fy, double cx, double cy, const Sophus::SE3d &pose):
    fx_(fx), fy_(fy), cx_(cx), cy_(cy), pose_(pose)
    {}  
// 内置类型 double 使用 pass-by-value; Sophus库自定义类型使用 pass-by-reference-const
public:
    cv::Mat toMat(const Sophus::SE3d &T_se3_mat);

    /***不全***/

};  // Camera

#endif  // CAMERA_H


🥝 条款02:尽量以const, enum, inline 替换 #define

宁可以编译器代替与处理器:

  • const全局常量代替宏定义#define  预编译报错远离我

  • 如果你被宏的小括号与莫名奇妙的结果逼疯了,如果你的函数体小且调用频繁,就在头文件用inline关键字修饰你的函数并实现它,成为放进编译器而不再需要在cpp中定义的内联函数。

  • 它是类专属的“宏定义”: enum{变量名 = 值}   // 不需要声明变量类型。如果你既希望全局的宏能有作用域,为某个类所私有,又能像宏一样得不到地址只需要得到值(并且暗示后来者与后来的自己这一点),那就用enum关键字

 inline函数一定置于头文件内,一定用在小型、被频繁调用的函数上

// converters 接口的函数体小,又会被频繁调用,所以用inline关键字修饰的内联函数
inline Vec2 toVec2(const cv::Point2f p) { return Vec2(p.x, p.y); }

f860a21e2c464b49b342baf2060a6320.png

P45声明后立刻要用它的值,就用enum解决

// orb-slam2使用enum关键字的例子
class Tack(){
public:
    // Tracking states
    enum eTrackingState{
        SYSTEM_NOT_READY=-1,
        NO_IMAGES_YET=0,
        NOT_INITIALIZED=1,
        OK=2,
        LOST=3
    };
};

……

// 用在了状态判断上:
if(mState==NOT_INITIALIZED || mState==NO_IMAGES_YET)

 inline关键字在以下几个类中被使用:ORBextractor ORB提取器(六个内联函数,用于计算特征点与描述子), Frame(获取相机中心,求旋转矩阵的逆), MapPoint(路标,ORB匹配上的点) 

// frontend.h

enum class FrontendStatus { INITING, TRACKING_GOOD, TRACKING_BAD, LOST };

// frontend.cpp

// bool Frontend::Track()
// 判断前端的状态是好的,还是有哪种错误
if (tracking_inliers_ > num_features_tracking_) {
        // tracking good
        status_ = FrontendStatus::TRACKING_GOOD;
    } else if (tracking_inliers_ > num_features_tracking_bad_) {
        // tracking bad
        status_ = FrontendStatus::TRACKING_BAD;
    } else {
        // lost
        status_ = FrontendStatus::LOST;
    }

// bool Frontend::AddFrame(type)
// 检查视觉里程计前端运行情况:位于Frontend类
switch (status_) {
        case FrontendStatus::INITING:
            StereoInit();
            break;
        case FrontendStatus::TRACKING_GOOD:
        case FrontendStatus::TRACKING_BAD:
            Track();
            break;
        case FrontendStatus::LOST:
            Reset();
            break;
}

🌈 条款03:尽可能用const

  1. 给对象加const,常量:如果你担心你的开发者用户以及你自己错误地对某变量赋值,那就在相应的作用域内使用const关键字(注意本书的“对象”包括变量)。(可以用在参数、全局/局部变量,甚至返回值上)
  2. 给指针加const,常量指针:const出现在"*"左边是给对象的,在右边是给指针的
  3. 给成员函数加const,常量成员函数:{函数体} 前加const,使其无法对类内成员赋值,除非是加了mutable关键字的成员

225dbb4c10214327882913ba2f3f266f.png

P49返回值加const有什么意义,一是避免赋值出错,二是作为注释提示这一性质

c473db91eee14c5ca4b779e7e4944166.png

P54利用const关键字修饰返回类型进行函数重载。常量对象调用返回常量对象的函数,反之非常量对象调用返回非常量对象的函数

🍊条款04: 确定对象使用前已被初始化

 构造函数写成列表初始化效率更高,注意顺序:初始化顺序与成员变量声名顺序有关,与初始化列表先后顺序无关,比如数组长度要在数组之前完成初始化

如果有跨编译单元(可以理解为不同的cpp文件)的初始化,就用Singleton模式,即设计一个全局函数,在这个函数内完成对象初始化,静态的,然后返回对象的引用(reference-returning)。

最妙的是该函数返回的是一个对象引用,后面可以直接接上成员函数。

db03841a5d5947f49f88e014c4fe053b.png

问题来了,为什么对象fs初始化一定要放在一个函数tfs()里呢?我没有找到有说服力的答案,我个人认为是因为比起在Directory构造函数内加一行fs初始化再使用fs.numDisks(),tfs().numDisks()只要一行,更优雅……

条款05 (大部分教程里都有,故略写)

成员变量不要使用引用和常量,否则赋值时将会出错(C++不允许reference改变指向,也不允许更改const成员)。

就算你不写,编译器也会自动生产defult构造函数,copy构造函数,析构函数,copy assignment操作符(也就是operator重载等号)

条款06 :明确地拒绝复制(这不是原标题,我自己总结的)

如果要杜绝对象copy(每一个对象都必然独一无二的时候)就显式地把copy构造函数、copy assignment操作符写在private下面。或者建一个Uncopyable基类继承它(见下图) 

9cc3475ff86f4bd49e85c190c0189e23.png

P69这样一来,任何复制行为都会以失败收场,错误将被及时发现

 orb-slam2中不能复制的东西很多,Camera, System, Tacking, Viewer但是作者完全没有考虑防复制

条款07:多态基类声明virtual析构函数 

多态基类ploymorphic base classes应该声明一个virtual析构函数,以免基类指针base pointer指向派生类derived class对象(背景:只想使用派生类类中基类的成员)然后delete释放指针时内存泄漏(解释:只释放了基类部分)。

如果class带有虚函数,最好有一个virtual析构函数,防止在遇到上一段的情形时出错

不是基类或无需多态的类,不该声明virtual析构函数

然而我在测试时发现,slam的作者对析构函数是完全忽略的,也就是在一个slam方案中,所以析构函数都是缺省实现的。所以我想看看slam要不要在对象析构的时候手动释放堆区内存,在哪里释放的?

很快我找到了,答案是利用智能指针管理堆区内存,有兴趣的读者参见我下一篇文章的条款13

条款08 :别让异常逃离析构函数

 在析构函数里出现问题,用try-catch结构吞掉这个错误

Class::~Class(){
    try{报错代码}
    catch(你发现的那个错误类型){
        /*生成报错日志*/
        std::abort();  // 强制结束程序,避免错误进一步传播导致令人眼花缭乱的报错页面
    }
}

🍉条款09:绝不在构造和析构函数内调用虚函数

因为派生类调用的是基类的构造函数,基类构造期间虚函数绝不会下降到派生类,用的还是基类的虚函数:“基类构造期间,虚函数不是虚函数”。析构函数同理。

派生类构造函数是总结可以参考这篇文章:C++派生类的构造函数总结-CSDN博客

不使用虚函数,将派生类必要的构造信息上传至基类构造函数。在派生类的成员初始列表中给予基类所需数据,构建private static辅助函数传递值(加辅助函数只是因为可读性高,加static关键字是因为静态成员编译阶段就分配内存,在构造中使用静态成员不会出现派生类还没构造、初始化就使用派生类成员的问题)

5a6b47e79b3c49efb4dc4742dca8f2f8.png

P81 其中Transaction是基类,BuyTransaction是派生类

 条款10:令operator= 返回一个reference to *this (只是代码规范)

  • 21
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值