Cpp工程实践必备技能


概述

本科时候质疑CMAKE,后面理解CMAKE,加入CMAKE。言归正传,之前不喜欢用clion的原因,一是因为clion每次使用的时候启动慢,加上卡,其次是那会真不会CMAKE,而clion是强制用,然后后面就选择用vs去了,到现在大部分时间都在linux下写代码,慢慢开始熟悉CMAKE,突然有点感慨,遇到这么多人大部分人还停留在学校用vs教个基础语法的阶段,modern cpp不了解,cmake更是不知道。

接下来会对现代工程进行一个初步介绍,对单元测试框架以及benchmark框架的引入和使用进行个预览。

CMAKE

首先是CMAKE,CMAKE写不好你都不要想着启动程序(迫真),目前来说,我觉得CMAKE不需要完全像一门编程语言一样能够熟悉到默写,只要能够看懂仿写我觉得就足够了。目前来说比较优秀的资料有这两个,两者都是类似讲座的形式,对现代CMAKE进行个粗略的介绍:一个半小时入门现代CMake现代CMake高级教程, 现代CMake模块化项目管理指南大概就能了解个差不多了。

单元测试框架

本来想用google test的,后面想想选择更加现代化的 catch2 或 doetest,两者都是head-only类型的,只需要引入就能够使用,不过更推荐用doctest,因为他编译速度比catch2快很多,如果想要使用方便的话直接使用 include(FetchContent)的方式使用即可。

 include(FetchContent)
 FetchContent_Declare(
         doctest
         GIT_REPOSITORY https://github.com/doctest/doctest.git
         GIT_TAG master
         GIT_SHALLOW TRUE
 )
 FetchContent_MakeAvailable(doctest)
 ​
 target_link_libraries(target doctest_with_main)

具体使用不在这讲,看官方文档即可,非常方便。

benchmark框架

建议使用 nanobench ,同样也是因为引入简单轻量,使用简单且 head only 。使用可以去看官方文档

include(FetchContent)
 ​
 FetchContent_Declare(
     nanobench
     GIT_REPOSITORY https://github.com/martinus/nanobench.git
     GIT_TAG master
     GIT_SHALLOW TRUE)
 ​
 FetchContent_MakeAvailable(nanobench)

具体输出的话如下:

  • ns/op:每个bench内容需要经历的时间(ns为单位)。
  • op/s:每秒可以执行多少次操作。
  • err%:运行多次测试的波动情况(误差)。
  • ins/op:每次操作需要多少条指令。
  • cyc/op:每次操作需要多少次时钟周期。
  • bra/op:每次操作有多少次分支预判。
  • miss%:分支预判的miss率。
  • total:本次消耗的总时间。
  • benchmark:对应的名字。

注意测试的时候,有时需要记得防止被编译器优化。

#include <nanobench.h>
#include <doctest/doctest.h>

TEST_CASE("tutorial_fast_v2") {
    uint64_t x = 1;
    ankerl::nanobench::Bench().run("++x", [&]() {
        ankerl::nanobench::doNotOptimizeAway(x += 1);
    });
}

内存泄露检测

推荐使用backward-cpp,同样是head-only类型的,同样的也可以用上面的方式引入。

include(FetchContent)

# Also requires one of: libbfd (gnu binutils), libdwarf, libdw (elfutils)
FetchContent_Declare(backward
    GIT_REPOSITORY https://github.com/bombela/backward-cpp
    GIT_TAG master  # or a version tag, such as v1.6
    GIT_SHALLOW TRUE
    SYSTEM          # optional, the Backward include directory will be treated as system directory
)
FetchContent_MakeAvailable(backward)

# Add Backward to your target (either Backward::Interface, Backward::Object, or Backward::Backward)
target_link_libraries(mytarget PUBLIC Backward::Interface)

对于perf,sanitizers等就不在这介绍了。

示例

├── build
├── include
├── src
│ ├── CMakeLists.txt
│ └── main.cpp
└── test
└── CMakeLists.txt

对于一个CMAKE构建的cpp工程应该具有类似上面的项目格式,在总目录下有 CMakeLists.txt,同时,在对应的src和test,有时还会有third-party目录下同样具有CMakeLists.txt。

cmake_minimum_required(VERSION 3.20)

if(NOT CMAKE_BUILD_TYPE)
  set(CMAKE_BUILD_TYPE "Debug")
endif()

project(main LANGUAGES CXX)

include(FetchContent)#用于引入上面介绍的三方库

include_directories(${PROJECT_SOURCE_DIR}/include)

set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

set(CMAKE_CXX_STANDARD 20)

add_subdirectory(src/)
add_subdirectory(test/)

总的项目cmakelists应该如上,当然对于第三方库的使用除了上面介绍的方法还有将文件download下来编译的,只不过现在介绍的方法更方便。

对应的src目录的cmakelists应该如下:

# track the stack info

# Also requires one of: libbfd (gnu binutils), libdwarf, libdw (elfutils)
FetchContent_Declare(
  backward
  GIT_REPOSITORY https://github.com/bombela/backward-cpp
  GIT_TAG master # or a version tag, such as v1.6
  GIT_SHALLOW TRUE
  SYSTEM # optional, the Backward include directory will be treated as system directory
)
FetchContent_MakeAvailable(backward)

# src
file(GLOB_RECURSE all_src CONFIGURE_DEPENDS *.cpp)

add_executable(main ${all_src})

target_include_directories(main PUBLIC ${PROJECT_SOURCE_DIR}/include/)
target_link_libraries(main PRIVATE Backward::Backward)

test的则是如下:

# doctest
FetchContent_Declare(
  doctest
  GIT_REPOSITORY https://github.com/doctest/doctest.git
  GIT_TAG master
  GIT_SHALLOW TRUE
)
FetchContent_MakeAvailable(doctest)

# benchmark
FetchContent_Declare(
  nanobench
  GIT_REPOSITORY https://github.com/martinus/nanobench.git
  GIT_TAG master
  GIT_SHALLOW TRUE)
FetchContent_MakeAvailable(nanobench)

file(GLOB_RECURSE all_tests *.cpp)
file(GLOB_RECURSE all_src CONFIGURE_DEPENDS ${PROJECT_SOURCE_DIR}/src/*.cpp)
list(REMOVE_ITEM all_src ${PROJECT_SOURCE_DIR}/src/main.cpp)

foreach(v ${all_tests})
  string(REGEX MATCH "test/.*" relative_path ${v})
  string(REGEX REPLACE "test/" "" target_name ${relative_path})
  string(REGEX REPLACE ".cpp" "" target_name ${target_name})

  add_executable(${target_name} ${v} ${all_src})
  target_include_directories(${target_name} PUBLIC ${PROJECT_SOURCE_DIR}/include/)
  target_link_libraries(${target_name} PRIVATE doctest_with_main nanobench Backward::Backward)
endforeach()

文章作者: JoyTsing
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 JoyTsing !
评论
  目录