Robotics Library项目代码分析(4)从Math库开始

RL 的源码阅读

前言

距离上次更新有不少时间了,最近工作比较忙,再加上有点生病了,所有晚上回来都挺累,没有时间更新,现在生病好了,继续开始更新。

从math库开始

我们先从src下的math库开始阅读,为啥呢,通过浏览cmakelists.txt文件,然后找到不同源代码项目子目录的顺序,然后依次查找看看,我们会发现,有些源代码作为基石搭在整体项目的底层,其他的代码都通过各种方式引用他们,而他们又不依赖其他的源文件目录的文件,其实就是这些代码没有引用其他写的代码,所有我们优先看这些源代码,从底向上看。

我们先从math的结构开始浏览
math主文件,下面共有metrics、spatial和一下直接放在math更目录的代码文件,然后还有一个CMakeLists.txt文件,我们和之前一样,从CMakeLists.txt开始看起来。

CMakeLists.txt

里面主要是制定了一些list的空间范围和一些编译的信息。有一些值得我们注意的。

  1. CMake版本检查

    if(NOT CMAKE_VERSION VERSION_LESS 3.0)
        add_library(math INTERFACE)
        add_custom_target(math_sources SOURCES ${HDRS})
    else()
        add_library(math STATIC ${HDRS} dummy.cpp)
    endif()
    
    • 如果CMake版本不低于3.0,添加一个INTERFACE类型的库 math,并创建一个自定义目标 math_sources,其源文件为 ${HDRS}
    • 如果CMake版本低于3.0,添加一个静态库 math,其源文件为 ${HDRS}dummy.cpp
    • INTERFACE主要用于定义编译选项、包含目录和链接库等属性,这些属性会传递给依赖它的目标,它不会生成任何二进制文件,说的直接就是它可以专门用来适用头文件,因为头文件不需要变成成二进制文件,直接使用源代码依赖,然后这个特性是CMake 3.0 才有的,所以我们要进行一次判定,当然如果是新项目可以直接指定Cmake最低版本直接拉到3以上(其实看项目要求,但是我个人认为改更新还是得更新)。当然这里既可以使用静态链接库而不是动态链接库,一般项目是可以任意动态链接或者静态链接,看开发团队的选择。
  2. 指针大小检查

    if(CMAKE_SIZEOF_VOID_P EQUAL 4)
        target_compile_definitions(math INTERFACE -DEIGEN_DONT_ALIGN_STATICALLY)
    endif()
    
    • 如果指针大小为4字节(即32位系统),为 math 库添加编译定义 -DEIGEN_DONT_ALIGN_STATICALLY。在32位系统上,某些数据结构的内存对齐可能会导致性能问题或崩溃。-DEIGEN_DONT_ALIGN_STATICALLY 编译定义告诉Eigen库不要对静态数据进行对齐优化。在64位系统上因为其对齐要求和机制通常更为宽松,所有不用担心这个问题,这个禁用之后会导致性能下降。
  3. CMake版本检查(继续)

    if(NOT CMAKE_VERSION VERSION_LESS 3.8)
        target_compile_features(math INTERFACE cxx_std_11)
    endif()
    
    • 如果CMake版本不低于3.8,为 math 库设置C++11标准。
  4. 包含目录设置

    target_include_directories(
        math
        INTERFACE
        $<BUILD_INTERFACE:${rl_SOURCE_DIR}/src>
        $<INSTALL_INTERFACE:$<INSTALL_PREFIX>/${CMAKE_INSTALL_INCLUDEDIR}/rl-${VERSION}>
        ${Boost_INCLUDE_DIR}
        ${EIGEN3_INCLUDE_DIRS}
    )
    
    • math 库设置包含目录:
      • BUILD_INTERFACE:构建时使用 ${rl_SOURCE_DIR}/src
      • INSTALL_INTERFACE:安装时使用 ${CMAKE_INSTALL_INCLUDEDIR}/rl-${VERSION}
      • 还包括 BoostEigen3 的包含目录。

metrics目录

metrics被包裹在namespace rl中的namespace math下面。

c++在20之前没有模块机制,在2024年的今天,模块也没有成为一个通用标准,在模块通用之前,使用头文件分离代码,然后使用不同的命名空间分离代码结构是主流的方案。这里采用了一个rl的命名空间,然后定义了一个math空间然后再定义了一个metrics空间。

我们看到这个结构体:
首先定义一个L2,然后在里面将Distance和Size分别定义成泛型T的value_type和size_type的两个别名。当然这里默认了T里面有这两个成员类型,在这里受限于当时的语法和标准,这里面我们目前是看到没有任何限制的,也就是如果在这一层的泛型T泛化成没有value_type和size_type的对象,程序会错误,而且这种错误一般隐藏的非常非常深。

template<typename T>
   struct L2
   {
    typedef typename T::value_type Distance;
    
    typedef typename T::size_type Size;
    
    typedef T Value;
    
    Distance operator()(const Value& lhs, const Value& rhs) const
    {
     using ::std::begin;
     using ::std::end;
     
     auto first1 = begin(lhs);
     auto last1 = end(lhs);
     auto first2 = begin(rhs);
     
     Distance distance = Distance();
     
     while (first1 != last1)
     {
      Distance tmp = *first1 - *first2;
      distance += tmp * tmp;
      ++first1;
      ++first2;
     }
     
     return ::std::sqrt(distance);
    }
    
    Distance operator()(const Distance& lhs, const Distance& rhs, const Size& index) const
    {
     return ::std::abs(lhs - rhs);
    }
   };

这里可以使用 static_assert 或者 SFINAE(Substitution Failure Is Not An Error)技术来进行编译时检查。

使用 static_assert 进行编译时检查

你可以在结构体内部添加 static_assert 来检查 T 是否具有所需的成员类型:

template<typename T>
struct L2
{
    static_assert(std::is_same<typename T::value_type, typename T::value_type>::value, "T must have a member type named value_type");
    static_assert(std::is_same<typename T::size_type, typename T::size_type>::value, "T must have a member type named size_type");

    typedef typename T::value_type Distance;
    typedef typename T::size_type Size;
    typedef T Value;

    Distance operator()(const Value& lhs, const Value& rhs) const
    {
        using ::std::begin;
        using ::std::end;

        auto first1 = begin(lhs);
        auto last1 = end(lhs);
        auto first2 = begin(rhs);

        Distance distance = Distance();

        while (first1 != last1)
        {
            Distance tmp = *first1 - *first2;
            distance += tmp * tmp;
            ++first1;
            ++first2;
        }

        return ::std::sqrt(distance);
    }

    Distance operator()(const Distance& lhs, const Distance& rhs, const Size& index) const
    {
        return ::std::abs(lhs - rhs);
    }
};

使用 SFINAE 进行编译时检查

也可以使用 SFINAE 来进行更灵活的检查:

template<typename T, typename = void>
struct has_value_type : std::false_type {};

template<typename T>
struct has_value_type<T, std::void_t<typename T::value_type>> : std::true_type {};

template<typename T, typename = void>
struct has_size_type : std::false_type {};

template<typename T>
struct has_size_type<T, std::void_t<typename T::size_type>> : std::true_type {};

template<typename T>
struct L2
{
    static_assert(has_value_type<T>::value, "T must have a member type named value_type");
    static_assert(has_size_type<T>::value, "T must have a member type named size_type");

    typedef typename T::value_type Distance;
    typedef typename T::size_type Size;
    typedef T Value;

    Distance operator()(const Value& lhs, const Value& rhs) const
    {
        using ::std::begin;
        using ::std::end;

        auto first1 = begin(lhs);
        auto last1 = end(lhs);
        auto first2 = begin(rhs);

        Distance distance = Distance();

        while (first1 != last1)
        {
            Distance tmp = *first1 - *first2;
            distance += tmp * tmp;
            ++first1;
            ++first2;
        }

        return ::std::sqrt(distance);
    }

    Distance operator()(const Distance& lhs, const Distance& rhs, const Size& index) const
    {
        return ::std::abs(lhs - rhs);
    }
};

让我们继续往下看:这里就是对矩阵进行了一个偏特化,传入一个行向量的矩阵,其中这里的Distance就修正为其的范数,norm()函数用于计算矩阵或向量的范数,具体就是各个元素平方和的平方根,下面是返回Distance直接距离。这里也是一样,没有对L2传入的模板参数进行强校验,也就是进行约束。我们一样可以采用上面的几种方式对其进行约束。

   template<typename Scalar, int Rows, int Options, int MaxRows, int MaxCols>
   struct L2< ::Eigen::Matrix<Scalar, Rows, 1, Options, MaxRows, MaxCols>>
   {
    typedef Scalar Distance;
    
    typedef int Size;
    
    typedef ::Eigen::Matrix<Scalar, Rows, 1, Options, MaxRows, MaxCols> Value;
    
    Distance operator()(const Value& lhs, const Value& rhs) const
    {
     return (lhs - rhs).norm();
    }
    
    Distance operator()(const Distance& lhs, const Distance& rhs, const Size& index) const
    {
     return ::std::abs(lhs - rhs);
    }
   };

这一段和上面没有什么区别,重点在与多了一个*:

   template<typename Scalar, int Rows, int Options, int MaxRows, int MaxCols>
   struct L2< ::Eigen::Matrix<Scalar, Rows, 1, Options, MaxRows, MaxCols>*>
   {
    typedef Scalar Distance;
    
    typedef int Size;
    
    typedef ::Eigen::Matrix<Scalar, Rows, 1, Options, MaxRows, MaxCols>* Value;
    
    Distance operator()(const Value& lhs, const Value& rhs) const
    {
     return (*lhs - *rhs).norm();
    }
    
    Distance operator()(const Distance& lhs, const Distance& rhs, const Size& index) const
    {
     return ::std::abs(lhs - rhs);
    }
   };

在L2Squared中也是一样的,区别在于将L2中的返回值都返回其值平方,我个人是感觉这两个头文件可以合并起来。

  • 17
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值