C++最佳实践之编译篇

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

架构师技能9-深入mybatis:Creating a new SqlSession到查询语句耗时特别长

开篇语录&#xff1a;以架构师的能力标准去分析每个问题&#xff0c;过后由表及里分析问题的本质&#xff0c;复盘总结经验&#xff0c;并把总结内容记录下来。当你解决各种各样的问题&#xff0c;也就积累了丰富的解决问题的经验&#xff0c;解决问题的能力也将自然得到极大的…

【生活】浅浅记录

各位小伙伴们好鸭&#xff0c;今天不是技术文章&#xff0c;浅浅记录一下最近几个月的收获&#x1f60a; 新的一年&#xff0c;一起努力&#xff0c;加油加油&#xff01;

2024年【安全员-A证】免费试题及安全员-A证作业模拟考试

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 安全员-A证免费试题参考答案及安全员-A证考试试题解析是安全生产模拟考试一点通题库老师及安全员-A证操作证已考过的学员汇总&#xff0c;相对有效帮助安全员-A证作业模拟考试学员顺利通过考试。 1、【多选题】 《陕西…

黑色金属冶炼5G智能工厂数字孪生可视化管控系统,推进金属冶炼行业数字化转型

黑色金属冶炼5G智能工厂数字孪生可视化管控系统&#xff0c;推进金属冶炼行业数字化转型。随着科技的不断发展&#xff0c;数字化转型已经成为各行各业发展的必然趋势。金属冶炼行业作为传统工业的重要组成部分&#xff0c;也面临着数字化转型的挑战和机遇。为了推进金属冶炼行…

Vue知识学习

Vue 是什么&#xff1f; 概念&#xff1a;Vue 是一个用于构建用户界面的渐进式框架 Vue 的两种使用方式: ① Vue 核心包开发 场景:局部 模块改造 ② Vue 核心包& Vue插件工程化开发 场景:整站开发 创建Vue 实例&#xff0c;初始化渲染的核心步骤: 1.准备容器 2.引包(官…

这两招,让你轻松俘获客户心

面向政府的数字化解决方案作为睿鸿数字应用的一个分支&#xff0c;在充分借鉴政府项目中积累的丰富经验的基础上&#xff0c;积极开发更多领域通用的标准化产品。 2023年&#xff0c;睿鸿推出了一系列创新的数字应用产品&#xff0c;包括动态表单系统、统一集成门户、统一通信中…

【MySQL】如何理解索引(高频面试点)

一、前言 首先这个博客会介绍一些关于MySQL中索引的基本内容以及一些基本的语法&#xff0c;当然里面也会有些常见的面试题的解答。 二、关于索引 1、概念 索引是一种能够帮助MySQL高效的去磁盘检索数据的一种数据结构。在MySQL的Innodb存储引擎中呢&#xff0c;采用的是B树的…

如何开发通过蓝牙技术实现灯光智能调节的小程序

近年来&#xff0c;随着智能家居市场日渐兴起&#xff0c;人们对于家居生活品质的需求不断提升。在这一背景下&#xff0c;蓝牙智能调节小程序的开发能够为客户提供更便捷、智能化的LED灯光调节方案。需求方想要实现通过蓝牙开发实现LED灯光亮度和颜色的智能调节&#xff0c;同…

NOW 闹个元宵?与亚信安慧AntDB一起猜灯谜,抽奖品

关于亚信安慧AntDB数据库 AntDB数据库始于2008年&#xff0c;在运营商的核心系统上&#xff0c;服务国内24个省市自治区的数亿用户&#xff0c;具备高性能、弹性扩展、高可靠等产品特性&#xff0c;峰值每秒可处理百万笔通信核心交易&#xff0c;保障系统持续稳定运行超十年&a…

关于使用Mxnet GPU版本运行DeepAR报错解决方案

1.引言 我们经常使用GPU来训练和部署神经网络&#xff0c;因为与CPU相比&#xff0c;它提供了更多的计算能力。在本教程中&#xff0c;我们将介绍如何将GPU与MXNet GluonTS一起使用。 首先&#xff0c;确保您的机器中至少有一个Nvidia GPU&#xff0c;并正确安装了CUDA以及CUDN…

SORA技术报告快速解读——浅谈其AIGC积累的技术底蕴

SORA技术报告解读 文章目录 概要SORA整体概要关键性的技术方案解析1. 视觉类型的特征嵌入和处理-video encoder1.1 压缩视频的特征网络模型是什么&#xff1f;1.2 如何处理不同分辨率的训练和推理问题&#xff1f;2 Scaling transformers 扩散模型3. 生成解码器 小结 概要 最…

深度学习基础(三)循环神经网络(RNN)

之前的章节我们初步介绍了卷积神经网络&#xff08;CNN&#xff09;&#xff1a; 深度学习基础&#xff08;二&#xff09;卷积神经网络&#xff08;CNN&#xff09;-CSDN博客文章浏览阅读2次。卷积神经网络&#xff08;CNN&#xff09;的应用领域广泛&#xff0c;尤其在图像处…

跟着野火学FreeRTOS:第二段(事件组)

在小节里面介绍了二进制信号量&#xff0c;计数信号量&#xff0c;互斥量和递归互斥量等功能&#xff0c;其中二进制信号量和计数信号量&#xff08;也包括队列&#xff09;常用于任务和任务之间以及任务和中断之间的同步&#xff0c;她们具有以下属性&#xff1a; 当等待的事…

Sora----打破虚实之间的最后一根枷锁----这扇门的背后是人类文明的晟阳还是最后的余晖

目录 一.Sora出道即巅峰 二.为何说Sora是该领域的巨头 三.Sora无敌的背后究竟有怎样先进的处理技术 1.Spacetime Latent Patches 潜变量时空碎片&#xff0c;建构视觉语言系统 2.扩散模型与Diffusion Transformer&#xff0c;组合成强大的信息提取器 3.DiT应用于潜变量时…

每日五道java面试题之spring篇(三)

目录&#xff1a; 第一题 ApplicationContext和BeanFactory有什么区别&#xff1f;第二题 Spring中的事务是如何实现的&#xff1f;第三题 Spring中什么时候Transactional会失效&#xff1f;第四题 Spring容器启动流程是怎样的&#xff1f;第五题 Spring Boot、Spring MVC 和 S…

隐藏饿了么el-select组件的el-select-dropdown部分,只使用el-select的显示框

隐藏饿了么el-select组件的el-select-dropdown部分,只使用el-select的显示框 问题: 由于el-select组件的el-select-dropdown部分是自动插入在最外层Body上的&#xff0c;所以在当前组件的scoped中让el-select-dropdown组件display:none不会生效所以需要&#xff1a; :popper-…

ZS Associates致盛咨询是什么公司?排名怎么样?

随着商业化时代的加速演进&#xff0c;咨询公司在企业发展中的“智囊团”角色愈发突显。对于医药企业来说&#xff0c;一个优秀的咨询团队不仅可以帮助推动整体战略转型及内部改革&#xff0c;还对药品研发、营销起到优化促进作用。 那什么样的咨询企业可称之为优秀的咨询企业…

【Java EE初阶二十】http的简单理解(一)

1. 初识http HTTP 最新的版本应该是 HTTP/3.0&#xff0c;目前大规模使用的版本 HTTP/1.1&#xff1b; 下面来简单说明一下使用 HTTP 协议的场景: 1、浏览器打开网站 (基本上) 2、手机 APP 访问对应的服务器 (大概率) 前面的 TCP与UDP 和http不同&#xff0c;HTTP 的报文格式&a…

【方法】PDF如何与其它格式文件互相转换?

在工作上&#xff0c;有时候我们需要把PDF文件转换成其他格式的文件&#xff0c;比如Word、PPT、jpg等&#xff0c;或者是其他格式文件转换成PDF&#xff0c;那具体要如何操作呢&#xff1f;不清楚的小伙伴一起来看看吧&#xff01; 想把PDF文件转换成其他格式文件&#xff0c…

GoLand 相关

goland 下载依赖 go mod tidy&#xff1a;保持依赖整洁 go mod tidy 命令的作用是清理未使用的依赖&#xff0c;并更新 go.mod 以及 go.sum 文件。 go mod tidy 和 go mod vendor 两个命令是维护项目依赖不可或缺的工具。go mod tidy 确保了项目的 go.mod 文件精简且准确&…