1.C++工程结构
参考:cxx-pflR1: The Pitchfork Layout (PFL) (csswg.org)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| . ├── 3rdparty -- 所有的第三方库目录 │ ├── external -- 源码依赖的第三方库 │ │ └── CMakeLists.txt │ └── target -- 头文件/库依赖的第三方库 │ └── catch │ └── catch.hpp ├── build -- 本地构建目录 │ ├── bin │ │ ├── Debug │ │ └── Release │ ├── docs │ └── lib ├── CMakeLists.txt -- 根目录cmake配置 ├── Doxyfile.in -- Doxygen配置文件 ├── include -- 公共头文件 │ └── netapi │ └── tools │ └── tools.hpp ├── README.md -- 说明文档 ├── src -- 源码目录 │ ├── CMakeLists.txt │ ├── main.cpp │ └── tools │ └── tools.cpp └── tests -- 测试源码目录 ├── CMakeLists.txt └── tools └── tools.test.cpp
|
物理组件组织分为分离和合并两种方式,一般作为库时,可以采用分离头文件的方式,但两种方式都要保证相对路径一致。
include目录中的项目名是防止在依赖第三方库时的名字冲突。
每个组件包含一个独立的命令空间,如gno::shapes::circle,可以从这个命名空间中得到文件的目录信息如下:
1 2 3 4 5 6 7
| <root> src/geo/shapes/ circle.cpp include/<root>/geo/shapes/ circle.hpp tests/geo/shapes/ circle.test.cpp
|
如果每个类的函数实现过于复杂,可以根据函数名进行再次拆分,如下所示:
1 2 3 4 5 6 7 8 9 10
| geo::shapes::circle -> circumference() geo::shapes::circle -> area()
<root> src/geo/shapes/ circle.hpp circle.cpp circle.circumference.cpp circle.area.cpp circle.test.cpp
|
2.CMakeLists.txt文件
2.1 根目录
./CMakeLists.txt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| cmake_minimum_required(VERSION 3.16)
set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED True) set(CMAKE_CXX_EXTENSIONS OFF)
project(netapi LANGUAGES C CXX)
set(PROJECT_INCLUDE_DIR "${PROJECT_SOURCE_DIR}/include") set(PROJECT_SRC_DIR "${PROJECT_SOURCE_DIR}/src") set(PROJECT_3RD_TARGET_DIR "${PROJECT_SOURCE_DIR}/3rdparty/target") set(PROJECT_3RD_EXTERNAL_DIR "${PROJECT_SOURCE_DIR}/3rdparty/external")
if(CMAKE_BUILD_TYPE STREQUAL "") set(CMAKE_BUILD_TYPE "Release") endif() message(STATUS "Current build type is ${CMAKE_BUILD_TYPE}")
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/${CMAKE_BUILD_TYPE}") set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib/${CMAKE_BUILD_TYPE}") set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/${CMAKE_BUILD_TYPE}")
find_package(Doxygen) if (DOXYGEN_FOUND) message(STATUS "Start to build doxygen documentation") set(DOXYGEN_IN ${PROJECT_SOURCE_DIR}/Doxyfile.in) set(DOXYGEN_OUT ${CMAKE_BINARY_DIR}/Doxyfile) configure_file(${DOXYGEN_IN} ${DOXYGEN_OUT} @ONLY) add_custom_target( docs ALL COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_OUT} WORKING_DIRECTORY ${CMAKE_BINARY_DIR} COMMENT "STATUS Generating API documentation with Doxygen" VERBATIM ) else (DOXYGEN_FOUND) message(STATUS "Dexygen not installed") endif (DOXYGEN_FOUND)
add_subdirectory(3rdparty/external) add_subdirectory(src) add_subdirectory(tests)
|
2.2 外部依赖目录
external/CMakeLists.txt
1
| add_subdirectory(spdlog)
|
2.3 源文件目录
src/CMakeLists.txt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| add_library(tools OBJECT "") file(GLOB_RECURSE tools_sources CONFIGURE_DEPENDS "${PROJECT_SRC_DIR}/tools/*.cpp" "${PROJECT_INCLUDE_DIR}/netapi/tools/*.hpp") target_sources(tools PRIVATE "${tools_sources}") target_include_directories(tools PUBLIC "${PROJECT_INCLUDE_DIR}")
target_include_directories(tools PUBLIC "${PROJECT_3RD_EXTERNAL_DIR}/spdlog/include") target_link_libraries(tools PRIVATE spdlog::spdlog)
add_executable(netapi main.cpp) target_link_libraries(netapi PUBLIC tools) target_include_directories(netapi PUBLIC "${PROJECT_INCLUDE_DIR}")
add_custom_target(run COMMAND "$<TARGET_FILE:netapi>")
|
2.4 测试目录
tests/CMakeLists.txt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| enable_testing()
add_executable( unit_tests tools/tools.test.cpp )
target_include_directories(unit_tests PUBLIC "${PROJECT_3RD_TARGET_DIR}")
target_include_directories(unit_tests PUBLIC "${PROJECT_INCLUDE_DIR}") target_link_libraries(unit_tests PRIVATE tools)
add_test(test_all unit_tests)
add_custom_target(test COMMAND "$<TARGET_FILE:unit_tests>")
|
3.catch2测试库
基础框架
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| #define CATCH_CONFIG_MAIN #include <catch/catch.hpp>
#include <netapi/tools/tools.hpp>
TEST_CASE("simple1") { int res = calc(1, 2); REQUIRE(res == 3); }
TEST_CASE("simple2") { int res = calc(3, 4); REQUIRE(res == 7); }
|
4.测试与运行
1 2 3 4 5 6 7 8 9 10
| cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build --parallel 4
cmake --build build --target run
cmake --build build --target test
cmake --build build --target docs
|
5.常用命令
- add_executable:添加可执行文件
- add_library:生成库文件,可选参数(动态库SHARED、静态库STATIC、临时对象OBJECT),ALIAS可以为库取别名
- target_include_directories:为目标添加头文件路径,使<>可以找到,其中的作用域修饰符控制被链接后是否对链接者可见
- target_link_libraries:为目标添加库路径
- add_subdirectory:添加子目录,会依次执行子目录的CMakeLists,并且变量可以向子目录传递
6.导入三方库
6.1 源码依赖
可以使用git拉取源码到指定目录作为git仓库的子模块,如下:
1 2 3
| git init git submodule add -b <分支> <url.git> <path> eg: git submodule add -b v1.x https://github.com/gabime/spdlog.git 3rdparty/external/spdlog
|
如果三方库存在自己的CMakeLists.txt则只需在3rdparty/external的CMakeLists.txt中添加子目录,如下:
1
| add_subdirectory(spdlog)
|
之后使用target_include_directories和target_link_libraries即可使用,如下。
1 2
| target_include_directories(tools PUBLIC "${PROJECT_3RD_EXTERNAL_DIR}/spdlog/include") target_link_libraries(tools PRIVATE spdlog::spdlog)
|
6.2 find_package
从系统默认目录中搜索库的包配置文件OpenCVConfig.cmake(或OpenCV-config.cmake),如果找到了则变量XXX_INCLUDE_DIR和XXX_LIBRARIES分别被赋值为库的头文件和库文件路径
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| find_package(OpenCV)
find_package(OpenCV REQUIRED)
find_package(OpenCV REQUIRED COMPONENTS core videoio)
find_package(OpenCV REQUIRED OPTIONAL_COMPONENTS core videoio)
if(OpenCV_FOUND) message ("OpenCV found") else() message (FATAL_ERROR "Cannot find OpenCV") endif()
add_executable(third_party_include main.cpp)
target_include_directories( third_party_include PRIVATE ${OpenCV_INCLUDE_DIR} )
target_link_libraries( third_party_include PRIVATE ${OpenCV_SYSTEM_LIBRARY} ${OpenCV_FILESYSTEM_LIBRARY} )
|
如果库没有被安装在标准目录下,可以采用手动设置变量或设置环境变量值的形式指出正确路径,如下所示:
1
| set(OpenCV_DIR "D:/OpenCV")
|
这种方式适合作者提供配置文件并且已经提前安装到操作系统中的库。
6.3 引入已编译的库
静态库
1 2 3 4
| add_library(MyLib STATIC IMPORTED) set_target_properties(MyLib PROPERTIES IMPORTED_LOCATION_RELEASE ${CMAKE_CURRENT_SOURCE_DIR}/libMyLibRe.a IMPORTED_LOCATION_DEBUG ${CMAKE_CURRENT_SOURCE_DIR}/libMyLibDe.a)
|
带有依赖项的静态库
1 2 3 4 5
| add_library(MyLib2 STATIC IMPORTED) set_target_properties(MyLib2 PROPERTIES IMPORTED_LOCATION_RELEASE ${CMAKE_CURRENT_SOURCE_DIR}/libMyLib2Re.a IMPORTED_LOCATION_DEBUG ${CMAKE_CURRENT_SOURCE_DIR}/libMyLib2De.a IMPORTED_LINK_INTERFACE_LIBRARIES MyLib)
|
动态库
1 2 3 4 5
| add_library(MyLib SHARED IMPORTED) set_property(TARGET MyLib PROPERTY IMPORTED_LOCATION c:/path/to/MyLib.dll) set_property(TARGET MyLib PROPERTY IMPORTED_IMPLIB c:/path/to/MyLib.lib) add_executable(myexe src1.c src2.c) target_link_libraries(myexe MyLib)
|
7.Doxygen注释
7.1 配置文件
doxygen -g命令生成默认Doxyfile
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
| PROJECT_NAME = "JNZ test project"
PROJECT_NUMBER = "1.0.0" PROJECT_BRIEF = "xxx"
OUTPUT_DIRECTORY = docs
INPUT = @PROJECT_SOURCE_DIR@/src \ @PROJECT_SOURCE_DIR@/include
OUTPUT_LANGUAGE = Chinese DOXYFILE_ENCODING = UTF-8
FILE_PATTERNS = *.hpp *.cpp
RECURSIVE = YES
OPTIMIZE_OUTPUT_FOR_C = NO
EXTRACT_ALL = yes EXTRACT_PRIVATE = yes EXTRACT_STATIC = yes
TYPEDEF_HIDES_STRUCT = YES
HIDE_SCOPE_NAMES = YES
QUIET = YES
EXAMPLE_RECURSIVE = YES
REFERENCED_BY_RELATION = YES REFERENCES_RELATION = YES REFERENCES_LINK_SOURCE = YES
GENERATE_LATEX = NO
HAVE_DOT = YES CALL_GRAPH = YES CALLER_GRAPH = YES
SOURCE_BROWSER = YES
GENERATE_HTML = YES
|
7.2 注释语法
7.2.1 注释格式
块注释
行注释
7.2.2 常用注释命令
命令 |
说明 |
@file |
指定文件名 |
@brief |
概要信息 |
@details |
详细描述 |
@todo |
将要做的事情,链接到汇总的TODO 列表 |
@warning |
警告信息 |
@exception |
注释异常对象 |
@bug |
缺陷,链接到所有缺陷汇总的缺陷列表 |
@since {text} |
通常用来说明从什么版本、时间写此部分代码 |
@code |
在注释中开始说明一段代码 |
@endcode |
注释中代码段的结束 |
@pre |
用来说明代码项的前提条件 |
@post |
用来说明代码项之后的使用条件 |
@see |
指定对其他代码项的引用链接 |
@note |
开始一个段落,用来描述一些注意事项 |
@par |
开始一个段落,段落名称描述由你自己指定 |
@include |
包含文件 |
@deprecated |
已废弃函数 |
@fn |
函数说明 |
@param |
解释函数参数 |
@return |
描述返回意义 |
@retval |
描述返回值具体意义 |
@var |
变量 |
@enum |
枚举 |
@struct |
结构体 |
@class |
类 |
7.2.3 项目整体注释
对于应用程序一般放置在main.cpp中,对于库程序一般放置在较关键但不会被引用的文件中,以下是注释框架:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
|
|
7.2.2 文件注释
7.2.3 函数注释