文章目录
- 1、使用cmake生成共享库及调用该库的可执行文件
-
- 项目结构
- [顶层 CMakeLists.txt](#顶层 CMakeLists.txt)
- lib/CMakeLists.txt
- lib/mylib.cpp
- include/mylib.h
- app/CMakeLists.txt
- app/main.cpp
- 构建项目
- 2、包含保护
- 3、指定共享库和可执行文件的保存路径
- 4、多个源文件处理
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
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
cmake
# 生成共享库
add_library(mylib SHARED mylib.cpp)
# 设置库的头文件路径
target_include_directories(mylib PUBLIC ${CMAKE_SOURCE_DIR}/include)
lib/mylib.cpp
cpp
#include "mylib.h"
#include <iostream>
void hello() {
std::cout << "Hello from the library!" << std::endl;
}
include/mylib.h
cpp
#ifndef MYLIB_H
#define MYLIB_H
void hello();
#endif // MYLIB_H
app/CMakeLists.txt
cmake
# 添加可执行文件
add_executable(myapp main.cpp)
# 链接共享库
target_link_libraries(myapp PRIVATE mylib)
app/main.cpp
cpp
#include "mylib.h"
int main() {
hello();
return 0;
}
构建项目
-
创建并进入构建目录:
shmkdir build cd build
-
生成构建文件:
shcmake ..
-
构建项目:
shmake
这样,你将生成一个共享库 libmylib.so
以及一个调用该共享库的可执行文件 myapp
。执行 myapp
时,它将输出 "Hello from the library!"。
整个项目通过使用 CMake 实现了生成共享库和调用该库的可执行文件的功能。你可以根据需要进一步扩展和调整这个项目的结构和内容。
关于生成链接库,public,private,interface的区别如下:
在 CMake 中,target_link_libraries
命令用于为目标添加库依赖项。PRIVATE
和 PUBLIC
是访问级别指示符,用于控制依赖关系传播的范围。它们的区别在于:
-
PRIVATE:
- 表示库的链接仅对当前目标可见,不会传播到依赖该目标的其他目标。
- 仅当前目标需要知道所链接的库。
-
PUBLIC:
- 表示库的链接对当前目标和依赖当前目标的其他目标都可见。
- 链接库不仅影响当前目标,还会传播到依赖该目标的其他目标。
-
INTERFACE(虽然你没提到,但为了完整性也解释一下):
- 表示库的链接仅对依赖当前目标的其他目标可见,而不影响当前目标本身。
- 当前目标不需要该库,但依赖当前目标的目标需要。
下面是一个简单的示例,演示这三者的区别:
cmake
# 顶层 CMakeLists.txt
cmake_minimum_required(VERSION 3.10)
project(LinkLibrariesExample)
add_subdirectory(libA)
add_subdirectory(libB)
add_subdirectory(app)
libA/CMakeLists.txt
cmake
add_library(libA STATIC libA.cpp)
target_include_directories(libA PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include)
libA/libA.cpp
cpp
#include <iostream>
void funcA() {
std::cout << "Function A" << std::endl;
}
libA/include/libA.h
cpp
#ifndef LIBA_H
#define LIBA_H
void funcA();
#endif // LIBA_H
libB/CMakeLists.txt
cmake
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
cpp
#include "libA.h"
#include <iostream>
void funcB() {
funcA();
std::cout << "Function B" << std::endl;
}
libB/include/libB.h
cpp
#ifndef LIBB_H
#define LIBB_H
void funcB();
#endif // LIBB_H
app/CMakeLists.txt
cmake
add_executable(myapp main.cpp)
target_link_libraries(myapp PRIVATE libB) # myapp 依赖 libB
app/main.cpp
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
cpp
#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
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
cpp
#pragma once
// 头文件内容
void myFunction();
优缺点
优点:
- 简洁 :
#pragma once
使用起来比传统的包含保护更加简洁,没有定义宏的麻烦。 - 减少错误:避免了手动管理宏名称的错误,例如拼写错误或名称冲突。
缺点:
- 移植性 :虽然大多数现代编译器都支持
#pragma once
,但它不是 C++ 标准的一部分。在一些非常老旧或特定的编译器上可能不被支持。
兼容性
目前,几乎所有主流编译器(如 GCC、Clang 和 MSVC)都支持 #pragma once
,因此在大多数情况下,它是一个安全且有效的选择。
对比示例
使用包含保护(include guard)
cpp
#ifndef MYHEADER_H
#define MYHEADER_H
// 头文件内容
void myFunction();
#endif // MYHEADER_H
使用 #pragma once
cpp
#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
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_LIST
src/mylib.cpp
)
# 添加共享库
add_library(mylib SHARED ${SRC_LIST})
# 设置可执行文件源文件
set(MAIN_SRC
src/main.cpp
)
# 添加可执行文件
add_executable(my_executable ${MAIN_SRC})
# 链接共享库到可执行文件
target_link_libraries(my_executable PRIVATE mylib)
src/mylib.cpp
cpp
#include "mylib.h"
#include <iostream>
void myFunction() {
std::cout << "Hello from myFunction!" << std::endl;
}
include/mylib.h
cpp
#pragma once
void myFunction();
src/main.cpp
cpp
#include "mylib.h"
int main() {
myFunction();
return 0;
}
构建项目
-
在项目根目录创建
build
目录并进入:shmkdir build cd build
-
运行 CMake 配置和生成:
shcmake .. 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
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_SRC
src/main.cpp
)
# 添加可执行文件
add_executable(my_executable ${MAIN_SRC})
# 链接共享库到可执行文件
target_link_libraries(my_executable PRIVATE mylib)
src/lib/mylib.cpp
cpp
#include "mylib.h"
#include <iostream>
void myFunction() {
std::cout << "Hello from myFunction!" << std::endl;
}
include/mylib.h
cpp
#pragma once
void myFunction();
src/lib/anotherlib.cpp
cpp
#include "anotherlib.h"
#include <iostream>
void anotherFunction() {
std::cout << "Hello from anotherFunction!" << std::endl;
}
include/anotherlib.h
cpp
#pragma once
void anotherFunction();
src/main.cpp
cpp
#include "mylib.h"
#include "anotherlib.h"
int main() {
myFunction();
anotherFunction();
return 0;
}
构建项目
-
在项目根目录创建
build
目录并进入:shmkdir build cd build
-
运行 CMake 配置和生成:
shcmake .. cmake --build .
结果
构建完成后,你将在 lib
目录下找到生成的共享库文件 libmylib.so
,在 bin
目录下找到生成的可执行文件 my_executable
。
这个方法使用了 file(GLOB_RECURSE ...)
来递归收集指定目录下的所有 .cpp
文件,你也可以根据需要调整目录路径和文件扩展名。