C++最佳实践之编译篇

news2024/11/19 8:34:03

C++最佳实践之工程编译

在大型c/c++工程开发中,往往会涉及多级CMakeLists.txt的调用,并且调用方式错综复杂,主要有以下两种方式:

1. 子目录中的CMakeList.txt独立生成目标,不作为主目标生成过程的依赖关系(比如dev_tool、driver、ut_test等),与主目标并无任何关系。
 
2. 子目录中的CMakeList.txt作为主目标的依赖源文件,不单独生成目标,作为主目标生成过程主的部分源文件,通常以生成.a源文件的方式提供给主CMakeList.txt使用。

一、工程目录结构

下面给出了测试工程目录,进行了两项测试:

  • unit—test目录作为独立生成目标,其CMakeLists.txt在主CMakeList.txt主被调用;

  • subfunc和subsubfunc作为主CMakeList.txt向下两级的依赖,为主CMakeList.txt提供源文件支持,其CMakeLists.txt为逐级调用的方式:
    CMakeList.txt->subfunc/CMakeList.txt->subfunc/subfuncfunc/CMakeLists.txt

具体目录结构如下:

在这里插入图片描述

  • build: 编译目录,生成的目标执行文件、静态库、中间缓存文件都在此处
  • inc:主头文件目录
  • src:主源文件目录
  • subfunc:依赖的一级子目录
  • uinit-test:单元测试目录,独立生成的目标文件
  • CMakeList.txt:最上层,主CMakeList.txt

二、工程源代码

2.1、工程源代码
cmake_minimum_required(VERSION 3.8)       # cmake 版本
PROJECT(cmaketest)                        # 工程名

#set project name
set(PROJECT_NAME cmaketest)               # 设置工程名字

set(CMAKE_CXX_STANDARD 17)                # 设置C++标准为C++17
set(CMAKE_CXX_STANDARD_REQUIRED ON)       

# 设置本地头文件路径,注意:子目录的头文件是通过target_include_directories添加到${PROJECT_NAME}中
INCLUDE_DIRECTORIES(
    inc                                   #上层头文件
    ${SUB_INCLUDE_DIR}                    #下级头文件
)

#将源文件路径添加到变量src_list中
AUX_SOURCE_DIRECTORY(.          SRC_LIST)
AUX_SOURCE_DIRECTORY(src        SRC_LIST)

# 7.生成目标(可执行文件):cmaketest
ADD_EXECUTABLE(${PROJECT_NAME} ${SRC_LIST})

# 8.设置编译时依赖的subfunc静态库
target_link_libraries(${PROJECT_NAME}    #目标:tcu
    subfunc        # sub子目录下的静态库文件
    subsubfunc     # subsub子目录下的静态库文件
)

# 9.添加子目录,这样子目录中的CMakeLists.txt才会被调用

# 调用subfunc子目录中的CMakeLists.txt,生成静态库而不生成新目标,目标与主CMakeLists.txt中设定的一致
add_subdirectory(subfunc) 
# 调用unit-test子目录中的CMakeLists.txt,生成新目标,目标与主CMakeLists.txt中设定的无关,仅仅是调用
add_subdirectory(unit-test)

注意

include_directories包含的头文件路径可以被各级子目录中的目标所引用;

target_include_directories包含的头文件只能被特定目标使用;

采用变量传递的方式(${sub_include_dir}引入子路径)引入子目录的头文件路径

cmakettest/main.cpp:

#include <iostream>
#include <string>
#include "func1.hpp"   //应用层头文件1
#include "func2.hpp"   //应用层头文件2

int main(int argc, char *argv[])
{
    func1();          //调用上层func1
    func2();          //调用上层func2
    return 0;
}

cmakettest/inc/func1.hpp:

#ifndef __FUNC1_HPP__
#define __FUNC1_HPP__

int func1(void);

#endif

cmakettest/inc/func2.hpp:

#ifndef __FUNC2_HPP__
#define __FUNC2_HPP__

int func2(void);

#endif

cmakettest/src/func1.cpp:

#include "subfunc.hpp"   //subfunc头文件
#include "func1.hpp"     //应用层头文件1
#include <iostream>
#include <string>

int func1(void)
{
    std::cout<<"------------func1函数调用开始----------"<<std::endl;
    subfunc1();
    std::cout<<"------------func1函数调用结束----------"<<std::endl<<std::endl;
    return 0;
}

cmakettest/src/func2.cpp:

#include "subfunc.hpp"   //subfunc头文件
#include "func2.hpp"     //应用层头文件1
#include <iostream>
#include <string>

int func2(void)
{
    std::cout<<"------------func2函数调用开始----------"<<std::endl;
    subfunc2();
    std::cout<<"------------func2函数调用结束----------"<<std::endl;
    return 0;
}
2.2、subfunc以及subsubfunc子目录

cmaketest/subfunc/CMakeLists.txt

# 1.将本目录下的所有.c 文件添加到SUB_DIR_LIB_SRCS变量
AUX_SOURCE_DIRECTORY(. SUB_DIR_SRC_LIST)

# 2.设置当前的头文件路径
set(SUB_INCLUDE_DIR 
    ${CMAKE_CURRENT_SOURCE_DIR}          # 当前源文件路径
    ${SUB_SUB_INCLUDE_DIR}               # 由下层subsubfunc目录传递的头文件路径
    CACHE INTERNAL "subfunc include dir" # 这个字符串相当于对变量SUB_INCLUDE_DIR的描述说明,不能省略,但可以自己随便定义,只有添加了这个描述SUB_INCLUDE_DIR变量才能被上层CMakeLists.txt调用!!!
)

MESSAGE(STATUS "subfunc层头文件路径 :${SUB_INCLUDE_DIR}")

# 3.生成静态库
add_library(subfunc ${SUB_DIR_SRC_LIST})

# 4.添加subsubfunc子目录,这样子目录中的CMakeLists.txt才会被调用
add_subdirectory(subsubfunc)

cmaketest/subfunc/subfunc.hpp:

#ifndef __SUB_FUNC_HPP__
#define __SUB_FUNC_HPP__

int subfunc1(void);
int subfunc2(void);

#endif

cmaketest/subfunc/subfunc.cpp:

#include "subfunc.hpp"
#include "subsubfunc.hpp"
#include <iostream>
#include <string>

int subfunc1(void)
{
    std::cout<<"------subfunc1函数调用开始------"<<std::endl;
    /* 中间调用subsubfunc1函数 */
subsubfunc1();
    std::cout<<"------subfunc1函数调用结束------"<<std::endl;
    return 0;
}
int subfunc2(void)
{
    std::cout<<"------subfunc2函数调用开始------"<<std::endl;
subsubfunc2();

    /* 中间调用subsubfunc2函数 */

    std::cout<<"------subfunc2函数调用结束------"<<std::endl;
    return 0;
}

cmaketest/subfunc/subsubfunc/CMakeLists.txt

# 1.将本目录下的所有.c 文件添加到SUB_DIR_LIB_SRCS变量
AUX_SOURCE_DIRECTORY(. SUB_SUB_DIR_SRC_LIST)

# 2.设置当前的头文件路径
set(SUB_SUB_INCLUDE_DIR 
    ${CMAKE_CURRENT_SOURCE_DIR}              # 当前源文件路径
    CACHE INTERNAL "subsubfunc include dir"  # 这个字符串相当于对变量SUB_SUB_INCLUDE_DIR的描述说明,不能省略,但可以自己随便定义,只有添加了这个描述SUB_SUB_INCLUDE_DIR变量才能被上层CMakeLists.txt调用!!!
)

MESSAGE(STATUS "subsubfunc层头文件路径 :${SUB_SUB_INCLUDE_DIR}")

# 3.生成静态库
add_library(subsubfunc ${SUB_SUB_DIR_SRC_LIST})

分析

1. 头文件目录变量的逐级向上传递,通过设置sub_sub_include_dir变量(必须包含cache internel “subsubfunc include dir”描述),将subsubfunc下的头文件路径传递给了上级subfunc的CMakeList.txt;

2. 通过设置SUB_INCLUDE_DIR变量(必须添加CACHE INTERNAL "subfunc include dir"描述),将subfunc文件下的头文件路径(包含之前获得的${SUB_SUB_INCLUDE_DIR})传递给了最上层文件下的CMakeLists.txt。
2.3、UT子目录

cmaketest/unit-test/CMakeLists.txt

add_executable(unit-test unit-test.cpp)
target_link_libraries(unit-test boost_system pthread)

cmaketest/unit-test/unit-test.cpp

#include <boost/asio.hpp> 
#include <iostream>

int main(int argc,char* argv[])
{
    std::cout<<"unit-test代码调用!!!"<<std::endl;
    return 0;
}

三、编译与实践

在项目路径下创建/build文件夹,用于存放cmake编译过程中生成的各种中间文件、库、最终目标文件等:

cd build
cmake ..
make

在cmake编译的过程中发现了一个问题,就是一开始执行cmake . .时候,并没有完成头文件变量的传递,导致make编译出错。执行结果如下:

在这里插入图片描述

make,结果如下:

在这里插入图片描述

可以看到,由于SUB_SUB_INCLUDE_DIR变量并没有传递到subfunc层的CMakeLists.txt,从而导致了在subfunc层并没有包含全部发头文件路径,导致编译的时候找不到subsubfunc.hpp。

解决方案:

经过反复测试发现,多执行几次cmake…(三次以上)就可以解决这个问题,猜测是因为多次执行cmake的过程完成了SUB_SUB_INCLUDE_DIR和SUB_INCLUDE_DIR变量向上层的传递:
在这里插入图片描述

四、build目录分析

在这里插入图片描述

  • unit-test作为独立的子目录,生成了独立的目标文件unit-test;
  • subfunc下生成了libsubfunc.a的静态库文件,在subsubfunc下生成了libsubsubfunc.a的静态库文件,这两个库文件供最上层CMakeLists.txt调用,并最终生成一个目标文件cmaketest;

五、程序执行结果

在这里插入图片描述

六、总结

1. 一种是独立的unit-test生成独立的目标文件,与主CMakeLists.txt仅有一个调用与被调用的关系,并不存在任何的编译依关系;

2. 另一种多级CMakeLists.txt调用之间存在上下级的依赖关系,下层的源代码给上层的调用提供支持(以生成静态库的方式),这里进行了分层设计。

3. 下层的头文件路径仅传递给调用的上一层而不会传递给最上层,这种变量传递的方式提高了代码的分层性,这里需要注意变量的设置(CACHE INTERNAL属性的必须添加)以完成下层到上层的变量传递,上述的两种分层CMakeLists.txt调用方式可覆盖基本的全部开发场景。

七、未完待续

下章将继续介绍C++相关的工程能力。

欢迎关注知乎:北京不北

欢迎关注douyin:near.X (北京不北)

欢迎+V:beijing_bubei

获得免费答疑,长期技术交流。

八、参考文献

,这里进行了分层设计。

3. 下层的头文件路径仅传递给调用的上一层而不会传递给最上层,这种变量传递的方式提高了代码的分层性,这里需要注意变量的设置(CACHE INTERNAL属性的必须添加)以完成下层到上层的变量传递,上述的两种分层CMakeLists.txt调用方式可覆盖基本的全部开发场景。

七、未完待续

下章将继续介绍C++相关的工程能力。

欢迎关注知乎:北京不北

欢迎关注douyin:near.X (北京不北)

欢迎+V:beijing_bubei

获得免费答疑,长期技术交流。

八、参考文献

https://blog.csdn.net/weixin_42700740/article/details/126364574

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1466633.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Windows+Yolo3-darknet训练自己数据集并测试

WindowsYolo3-darknet训练自己的数据集并测试 一、首要条件 Windows 7下配置好VS2015OPENCV3.4.2YOLO3CUDA10.0CUDNN7.5生成darknet.exe。具体配置可参考我的博客&#xff1a;https://blog.csdn.net/wszswllnzn_/article/details/100760477 二.制作数据集 1、方法1 使用软件la…

uView组件使用

u-collapse 折叠面板 https://www.uviewui.com/components/collapse.html?ivk_sa1024320u 如果是异步加载的数据&#xff0c;最开始的 u-collapse-item是默认高度&#xff0c;&#xff0c;第一次点开的时候&#xff0c;异步数据不能撑开高度 但是如果 u-collapse-item中有内…

BUGKU-WEB 备份是个好习惯

题目描述 题目截图如下&#xff1a; 进入场景看看&#xff1a; 解题思路 看源码看提示&#xff1a;备份是个好习惯扫描目录md5弱比较 相关工具 御剑md5解密&#xff1a;https://www.somd5.com/ 解题步骤 看到的这串字符&#xff0c;有点像md5&#xff1f; d41d8cd98…

X-Rhodamine maleimide ,ROX 马来酰亚胺,实验室常用的荧光染料

您好&#xff0c;欢迎来到新研之家 文章关键词&#xff1a;X-Rhodamine maleimide &#xff0c;X-Rhodamine mal&#xff0c;ROX-maleimide&#xff0c;ROX 马来酰亚胺 一、基本信息 【产品简介】&#xff1a;ROX, also known as Rhodamine 101, is a product whose active …

linux0.11 源码阅读 head.s setup.s bootsect.s加载位置

从github上下载linux0.11源码 linux0.11源码 将0x10000处的代码往下复制到0开始的地址处。 移动后的内存布局如下 setup中存在gdt和idt的相关数据。此时需要用gdtr和idtr寄存器指向对应的数据。 实模式下&#xff0c;访问内存方式。最多访问1M内存。 分页模式下&…

STM32控制max30102读取血氧心率数据(keil5工程)

一、前言 MAX30102是一款由Maxim Integrated推出的低功耗、高精度的心率和血氧饱和度检测传感器模块&#xff0c;适用于可穿戴设备如智能手环、智能手表等健康管理类电子产品。 该传感器主要特性如下&#xff1a; &#xff08;1&#xff09;光学测量&#xff1a;MAX30102内置…

Java向ES库中插入数据报错:I/O reactor status: STOPPED

Java向ES库中插入数据报错&#xff1a;java.lang.IllegalStateException: Request cannot be executed; I/O reactor status: STO 一、问题问题原因 二、解决思路 一、问题 在使用Java向ES库中插入数据时&#xff0c;第一次成功插入&#xff0c;第二次出现以下错误&#xff1a…

C++种pair的初始化及与unordered_map的区别

pair的初始化及与unordered_map的区别 概述pair初始化开发环境头文件示例运行结果 与unordered_map的区别 概述 本文旨在介绍pair初始化&#xff0c;同时简述pair与unordered_map的区别。 pair初始化 pair是一个模板类&#xff0c;可以存储两个类型的数据为一个对象。 开发…

【深度学习】SSD 神经网络:彻底改变目标检测

一、说明 Single Shot MultiBox Detector &#xff08;SSD&#xff09; 是一项关键创新&#xff0c;尤其是在物体检测领域。在 SSD 出现之前&#xff0c;对象检测主要通过两阶段过程执行&#xff0c;首先识别感兴趣的区域&#xff0c;然后将这些区域分类为对象类别。这种方法虽…

js数组操作大全

目录 创建数组&#xff1a; 访问和修改数组元素&#xff1a; 数组的遍历&#xff1a; 数组的操作&#xff1a; 数组的转换&#xff1a; 创建数组&#xff1a; 使用数组字面量&#xff1a;let arr []。使用new关键字和Array构造函数&#xff1a;let arr new Array()。 访问…

【更换yarn的位置】解决yarn和nodejs不在同一盘下产生的某些命令应用失败问题

具体问题我记得是command fail什么error&#xff0c;记不太清楚了&#xff0c;文章主要写了如何替换yarn路径&#xff0c;希望可以帮助到大家。

【算法与数据结构】链表、哈希表、栈和队列、二叉树(笔记二)

文章目录 四、链表理论五、哈希表理论五、栈和队列理论5.1 单调栈 六、二叉树理论6.1 树的定义6.2 二叉树的存储方式6.3 二叉树的遍历方式6.4 高度和深度 最近博主学习了算法与数据结构的一些视频&#xff0c;在这个文章做一些笔记和心得&#xff0c;本篇文章就写了一些基础算法…

10:部署Dashboard|部署Prometheus|HPA集群

部署Dashboard&#xff5c;部署Prometheus&#xff5c;HPA集群 Dashboard部署Dashboard上传镜像到私有仓库安装服务发布服务创建管理用户查看登录的Token信息 Prometheus步骤一&#xff1a;导入所有后续需要的镜像到私有镜像仓库&#xff08;在master主机操作操作&#xff09;步…

LangChain原理学习笔记

最新越发觉得AI的发展&#xff0c;对未来是一场革命&#xff0c;LangChain已经在工程设计上有了最佳实践&#xff0c;类似于AI时代的编程模型或编程框架&#xff0c;有点Spring框架的意思。之前在LangChain上也有些最佳实践&#xff0c;所以在这里分享记录下。 LangChain解决什…

计算机网络面经-TCP三次握手一文说清

目录 说一下TCP的三次握手&#xff1f; 为什么要三次握手&#xff1f;两次行不行&#xff1f;四次呢&#xff1f; 为什么建立连接是三次握手&#xff0c;关闭连接确是四次挥手呢&#xff1f; TCP四次挥手的过程&#xff1f; 如果已经建立了连接&#xff0c;但是客户端突然出…

编译GreatSQL with RocksDB引擎

GreatSQL里也能用上RocksDB引擎 1. 前言 RocksDB 是基于Facebook 开源的一种支持事务的、高度可压缩、高性能的MyRocks存储引擎&#xff0c;特别适用于高度压缩和大容量的数据。以下是一些关键特点&#xff1a; 高性能&#xff1a; LSM 树结构使得RocksDB在写入密集型负载下表现…

苹果分拣检测YOLOV8NANO

苹果分拣&#xff0c;可以检测成熟、切片、损坏、不成熟四种类型&#xff0c;YOLOV8NANO&#xff0c;训练得到PT模型&#xff0c;然后转换成ONNX&#xff0c;OPENCV的DNN调用&#xff0c;支持C,PYTHON 苹果分拣检测YOLOV8NANO&#xff0c;检测四种类型苹果

React18源码: React调度中的3种优先级类型和Lane的位运算

优先级类型 React内部对于优先级的管理&#xff0c;贯穿运作流程的4个阶段&#xff08;从输入到输出&#xff09;&#xff0c;根据其功能的不同&#xff0c;可以分为3种类型&#xff1a; 1 &#xff09;fiber优先级(LanePriority) 位于 react-reconciler包&#xff0c;也就是L…

【电路笔记】-RC放电电路

RC放电电路 文章目录 RC放电电路1、概述2、RC放电电路3、RC放电电路示例当电压源从完全充电的 RC 电路中移除时,电容器 C 将通过电阻 R 放电。 1、概述 RC 放电电路利用电阻器-电容器组合的固有 RC 时间常数以指数衰减率对电容器进行放电。 在之前的 RC 充电电路教程中,我们…

4 buuctf解题

[CISCN 2019 初赛]Love Math1 打开题目 题目源码 <?php error_reporting(0); //听说你很喜欢数学&#xff0c;不知道你是否爱它胜过爱flag if(!isset($_GET[c])){show_source(__FILE__); }else{//例子 c20-1$content $_GET[c];if (strlen($content) > 80) {die("…