概述
本科时候质疑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()