文章目录
- 1、使用cmake生成共享库及调用该库的可执行文件
- 项目结构
- 顶层 CMakeLists.txt
- lib/CMakeLists.txt
- lib/mylib.cpp
- include/mylib.h
- app/CMakeLists.txt
- app/main.cpp
- 构建项目
- libA/CMakeLists.txt
- libA/libA.cpp
- libA/include/libA.h
- libB/CMakeLists.txt
- libB/libB.cpp
- libB/include/libB.h
- app/CMakeLists.txt
- app/main.cpp
- 2、包含保护
- 具体解释
- 包含保护示例
- `myheader.h`
- 工作原理
- 实际效果
- `main.cpp`
- 现代替代方案
- pragma once 的作用
- 使用示例
- `myheader.h`
- 优缺点
- 兼容性
- 对比示例
- 使用包含保护(include guard)
- 使用 `#pragma once`
- 总结
- 3、指定共享库和可执行文件的保存路径
- 项目结构
- CMakeLists.txt
- `src/mylib.cpp`
- `include/mylib.h`
- `src/main.cpp`
- 构建项目
- 结果
- 4、多个源文件处理
- 示例项目结构
- `CMakeLists.txt`
- `src/lib/mylib.cpp`
- `include/mylib.h`
- `src/lib/anotherlib.cpp`
- `include/anotherlib.h`
- `src/main.cpp`
- 构建项目
- 结果
1、使用cmake生成共享库及调用该库的可执行文件
共享库文件.so,及调用该so的可执行文件,使用场景就是,so是用来提供给客户的,可执行文件是自己用来测试看so的效果的。
上代码。
这里是一个简单的C++项目的示例,它使用 CMake 来生成一个共享库(.so 文件)以及一个调用该共享库的可执行文件。
项目结构
MyProject/
├── CMakeLists.txt
├── lib/
│ ├── CMakeLists.txt
│ └── mylib.cpp
├── include/
│ └── mylib.h
└── app/├── CMakeLists.txt└── main.cpp
顶层 CMakeLists.txt
cmake_minimum_required(VERSION 3.10)# 项目信息
project(MyProject VERSION 1.0)# 设置C++标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)# 添加子目录
add_subdirectory(lib)
add_subdirectory(app)
lib/CMakeLists.txt
# 生成共享库
add_library(mylib SHARED mylib.cpp)# 设置库的头文件路径
target_include_directories(mylib PUBLIC ${CMAKE_SOURCE_DIR}/include)
lib/mylib.cpp
#include "mylib.h"
#include <iostream>void hello() {std::cout << "Hello from the library!" << std::endl;
}
include/mylib.h
#ifndef MYLIB_H
#define MYLIB_Hvoid hello();#endif // MYLIB_H
app/CMakeLists.txt
# 添加可执行文件
add_executable(myapp main.cpp)# 链接共享库
target_link_libraries(myapp PRIVATE mylib)
app/main.cpp
#include "mylib.h"int main() {hello();return 0;
}
构建项目
-
创建并进入构建目录:
mkdir build cd build
-
生成构建文件:
cmake ..
-
构建项目:
make
这样,你将生成一个共享库 libmylib.so
以及一个调用该共享库的可执行文件 myapp
。执行 myapp
时,它将输出 “Hello from the library!”。
整个项目通过使用 CMake 实现了生成共享库和调用该库的可执行文件的功能。你可以根据需要进一步扩展和调整这个项目的结构和内容。
关于生成链接库,public,private,interface的区别如下:
在 CMake 中,target_link_libraries
命令用于为目标添加库依赖项。PRIVATE
和 PUBLIC
是访问级别指示符,用于控制依赖关系传播的范围。它们的区别在于:
-
PRIVATE:
- 表示库的链接仅对当前目标可见,不会传播到依赖该目标的其他目标。
- 仅当前目标需要知道所链接的库。
-
PUBLIC:
- 表示库的链接对当前目标和依赖当前目标的其他目标都可见。
- 链接库不仅影响当前目标,还会传播到依赖该目标的其他目标。
-
INTERFACE(虽然你没提到,但为了完整性也解释一下):
- 表示库的链接仅对依赖当前目标的其他目标可见,而不影响当前目标本身。
- 当前目标不需要该库,但依赖当前目标的目标需要。
下面是一个简单的示例,演示这三者的区别:
# 顶层 CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(LinkLibrariesExample)add_subdirectory(libA)
add_subdirectory(libB)
add_subdirectory(app)
libA/CMakeLists.txt
add_library(libA STATIC libA.cpp)
target_include_directories(libA PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
libA/libA.cpp
#include <iostream>
void funcA() {std::cout << "Function A" << std::endl;
}
libA/include/libA.h
#ifndef LIBA_H
#define LIBA_Hvoid funcA();#endif // LIBA_H
libB/CMakeLists.txt
add_library(libB STATIC libB.cpp)
target_include_directories(libB PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
target_link_libraries(libB PUBLIC libA) # libB 依赖 libA
libB/libB.cpp
#include "libA.h"
#include <iostream>void funcB() {funcA();std::cout << "Function B" << std::endl;
}
libB/include/libB.h
#ifndef LIBB_H
#define LIBB_Hvoid funcB();#endif // LIBB_H
app/CMakeLists.txt
add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE libB) # myapp 依赖 libB
app/main.cpp
#include "libB.h"int main() {funcB();return 0;
}
在这个示例中:
-
libB
通过target_link_libraries(libB PUBLIC libA)
明确表示libA
对libB
是公共依赖。因此,依赖libB
的任何目标(如myapp
)也会自动链接libA
,并且可以访问libA
的头文件和符号。 -
如果
libB
通过target_link_libraries(libB PRIVATE libA)
表示libA
对libB
是私有依赖,那么myapp
不会自动链接libA
,也不能访问libA
的符号。 -
如果
libB
通过target_link_libraries(libB INTERFACE libA)
表示libA
对libB
是接口依赖,那么libB
本身不会链接libA
,但是任何依赖libB
的目标(如myapp
)会自动链接libA
。
这种控制方式可以帮助更精确地管理依赖关系,避免不必要的库链接,确保编译和链接的效率。
2、包含保护
在 C++ 编程中,#ifndef
、#define
和 #endif
常用于头文件的包含保护(include guard)。它们的作用是防止头文件被多次包含导致的重复定义问题。
具体解释
-
#ifndef
(if not defined):- 该指令检查指定的宏是否未被定义。如果未定义,则处理后续的代码段。
-
#define
:- 该指令定义一个宏,通常在
#ifndef
之后使用,以确保后续包含同一个头文件时宏已经被定义,从而避免重复包含。
- 该指令定义一个宏,通常在
-
#endif
:- 该指令结束一个
#ifndef
或其他预处理条件。
- 该指令结束一个
包含保护示例
下面是一个包含保护的典型示例:
myheader.h
#ifndef MYHEADER_H
#define MYHEADER_H// 头文件内容
void myFunction();#endif // MYHEADER_H
工作原理
-
第一次包含
myheader.h
时:#ifndef MYHEADER_H
检查MYHEADER_H
是否未被定义。由于它尚未定义,条件为真。#define MYHEADER_H
定义MYHEADER_H
,防止后续重复包含。- 包含头文件内容,定义
myFunction
。
-
第二次及后续包含
myheader.h
时:#ifndef MYHEADER_H
检查MYHEADER_H
是否未被定义。由于它已被定义,条件为假。- 直接跳过头文件内容,避免重复定义。
实际效果
在代码中使用包含保护可以防止头文件重复包含引起的编译错误,例如重复定义函数、变量或类。以下是一个示例,演示头文件包含保护的实际效果:
main.cpp
#include "myheader.h"
#include "myheader.h" // 重复包含,但不会导致问题int main() {myFunction();return 0;
}
在这个示例中,由于 myheader.h
使用了包含保护,尽管它被包含了两次,但编译器只会处理一次,不会出现重复定义的错误。
现代替代方案
C++20 引入了模块(modules),它提供了一种更现代化的头文件管理方式,可以从根本上避免这些问题。不过,包含保护仍然是目前广泛使用的解决方案,特别是在不支持模块的编译器和代码库中。
#pragma once
是一种用于防止头文件被多次包含的编译指示,它的作用与包含保护(include guard,即 #ifndef
、#define
、#endif
组合)类似,但更加简洁。
pragma once 的作用
当在一个头文件中使用 #pragma once
时,编译器会确保该头文件在一个编译单元中只会被处理一次,即使它被多次包含。这样可以防止重复定义的问题。
使用示例
myheader.h
#pragma once// 头文件内容
void myFunction();
优缺点
优点:
- 简洁:
#pragma once
使用起来比传统的包含保护更加简洁,没有定义宏的麻烦。 - 减少错误:避免了手动管理宏名称的错误,例如拼写错误或名称冲突。
缺点:
- 移植性:虽然大多数现代编译器都支持
#pragma once
,但它不是 C++ 标准的一部分。在一些非常老旧或特定的编译器上可能不被支持。
兼容性
目前,几乎所有主流编译器(如 GCC、Clang 和 MSVC)都支持 #pragma once
,因此在大多数情况下,它是一个安全且有效的选择。
对比示例
使用包含保护(include guard)
#ifndef MYHEADER_H
#define MYHEADER_H// 头文件内容
void myFunction();#endif // MYHEADER_H
使用 #pragma once
#pragma once// 头文件内容
void myFunction();
总结
#pragma once
提供了一种简单有效的方式来防止头文件的重复包含。如果你确定你的编译器支持它,那么它是一个很好的选择。不过,如果你需要兼容非常老旧的编译器,包含保护(include guard)可能是更安全的选择。
3、指定共享库和可执行文件的保存路径
可以通过在 CMake 文件中设置适当的输出目录来指定生成的共享库(.so
文件)和可执行文件的保存位置。你可以使用 CMAKE_LIBRARY_OUTPUT_DIRECTORY
和 CMAKE_RUNTIME_OUTPUT_DIRECTORY
来分别设置共享库和可执行文件的输出目录。
以下是一个简单的 C++ 项目的 CMake 示例,它生成一个共享库(.so
文件)和一个可执行文件,并指定它们的保存位置。
项目结构
my_project/
├── CMakeLists.txt
├── src/
│ ├── main.cpp
│ └── mylib.cpp
├── include/
│ └── mylib.h
└── build/
CMakeLists.txt
cmake_minimum_required(VERSION 3.10)# 项目信息
project(MyProject VERSION 0.1)# 设置C++标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)# 输出源路径和二进制路径
message(STATUS "src path: ${CMAKE_SOURCE_DIR}")
message(STATUS "binary path: ${CMAKE_BINARY_DIR}")# 设置库文件和可执行文件的输出目录
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/lib)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin)message(STATUS "Library output path: ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}")
message(STATUS "Executable output path: ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}")# 包含头文件目录
include_directories(${CMAKE_SOURCE_DIR}/include)# 设置源文件列表
set(SRC_LISTsrc/mylib.cpp
)# 添加共享库
add_library(mylib SHARED ${SRC_LIST})# 设置可执行文件源文件
set(MAIN_SRCsrc/main.cpp
)# 添加可执行文件
add_executable(my_executable ${MAIN_SRC})# 链接共享库到可执行文件
target_link_libraries(my_executable PRIVATE mylib)
src/mylib.cpp
#include "mylib.h"
#include <iostream>void myFunction() {std::cout << "Hello from myFunction!" << std::endl;
}
include/mylib.h
#pragma oncevoid myFunction();
src/main.cpp
#include "mylib.h"int main() {myFunction();return 0;
}
构建项目
-
在项目根目录创建
build
目录并进入:mkdir build cd build
-
运行 CMake 配置和生成:
cmake .. cmake --build .
结果
构建完成后,你将在 lib
目录下找到生成的共享库文件 libmylib.so
,在 bin
目录下找到生成的可执行文件 my_executable
。
4、多个源文件处理
如果项目的源文件分布在多个文件夹中,你可以使用 file(GLOB ...)
或 aux_source_directory
来收集这些文件夹中的源文件。
示例项目结构
my_project/
├── CMakeLists.txt
├── src/
│ ├── main.cpp
│ └── lib/
│ ├── mylib.cpp
│ └── anotherlib.cpp
├── include/
│ ├── mylib.h
│ └── anotherlib.h
└── build/
CMakeLists.txt
cmake_minimum_required(VERSION 3.10)# 项目信息
project(MyProject VERSION 0.1)# 设置C++标准
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED True)# 输出源路径和二进制路径
message(STATUS "src path: ${CMAKE_SOURCE_DIR}")
message(STATUS "binary path: ${CMAKE_BINARY_DIR}")# 设置库文件和可执行文件的输出目录
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/lib)
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/bin)message(STATUS "Library output path: ${CMAKE_LIBRARY_OUTPUT_DIRECTORY}")
message(STATUS "Executable output path: ${CMAKE_RUNTIME_OUTPUT_DIRECTORY}")# 包含头文件目录
include_directories(${CMAKE_SOURCE_DIR}/include)# 使用 GLOB 收集源文件
file(GLOB_RECURSE LIB_SOURCES"${CMAKE_SOURCE_DIR}/src/lib/*.cpp"
)# 设置源文件列表
set(SRC_LIST ${LIB_SOURCES})# 添加共享库
add_library(mylib SHARED ${SRC_LIST})# 设置可执行文件源文件
set(MAIN_SRCsrc/main.cpp
)# 添加可执行文件
add_executable(my_executable ${MAIN_SRC})# 链接共享库到可执行文件
target_link_libraries(my_executable PRIVATE mylib)
src/lib/mylib.cpp
#include "mylib.h"
#include <iostream>void myFunction() {std::cout << "Hello from myFunction!" << std::endl;
}
include/mylib.h
#pragma oncevoid myFunction();
src/lib/anotherlib.cpp
#include "anotherlib.h"
#include <iostream>void anotherFunction() {std::cout << "Hello from anotherFunction!" << std::endl;
}
include/anotherlib.h
#pragma oncevoid anotherFunction();
src/main.cpp
#include "mylib.h"
#include "anotherlib.h"int main() {myFunction();anotherFunction();return 0;
}
构建项目
-
在项目根目录创建
build
目录并进入:mkdir build cd build
-
运行 CMake 配置和生成:
cmake .. cmake --build .
结果
构建完成后,你将在 lib
目录下找到生成的共享库文件 libmylib.so
,在 bin
目录下找到生成的可执行文件 my_executable
。
这个方法使用了 file(GLOB_RECURSE ...)
来递归收集指定目录下的所有 .cpp
文件,你也可以根据需要调整目录路径和文件扩展名。