现代 C++ 开发涵盖广泛的应用程序——从高性能服务器和复杂的游戏引擎到人工智能驱动的系统和跨平台 GUI 软件。
选择正确的库可以决定项目的成功或失败,减少开发时间,保持代码库更干净,并确保软件面向未来。这篇文章探讨了一些最具影响力的 C++ 库和框架,用于创建健壮且可扩展的应用程序。
我们将讨论每个库的主要功能、用例和潜在限制;我们还将提供代码片段,以尽量减少原始指针的使用,并尽可能使用引用、智能指针和资源获取初始化 (RAII)。
C++ 简要概述
自 1983 年首次推出以来,C++ 已经取得了显着的发展,尤其是采用较新的标准(C++11、C++14、C++17、C++20 和 C++23)。自动、lambda、基于范围的循环和智能指针等现代语言功能提高了生产力和安全性。然而,该语言的真正力量通常来自精心设计的库,这些库封装了复杂的任务——从网络和 GUI 设计到 AI 和并发。
对于大中型组织中的 C++ 开发人员来说,可扩展性、可维护性、性能和高效协作等因素至关重要。使用已建立的 C++ 库将节省你的时间,而无需重新发明轮子、优化你的库并提高可靠性和可维护性。
让我们深入了解推荐的库列表,以解决现代 C++ 开发的各个方面。
Boost
Boost 通常被描述为 C++ 标准库的扩展。它便携且经过验证,为从字符串作到并发的所有内容提供了经过充分测试的库。随着时间的推移,Boost 的许多部分已经影响或进入了官方 C++ 标准。
Boost 广泛的库集合远远超出了其众所周知的组件,所有这些组件都是 Boost 补充标准库的经过同行评审的 C++ 库综合集合的一部分。
Boost 涵盖了从文件系统作到复杂数学的众多用例和应用程序。然而,这些具有陡峭的学习曲线,并且文档可能非常广泛。由于库的复杂性,构建时间也会受到影响。
基本用法代码段
下面是一个使用 boost::split 将字符串分解为子字符串的示例,避免原始指针:
#include <boost/algorithm/string.hpp>
#include <iostream>
#include <vector>
#include <string>
int main() {
std::string text = "Hello,Boost, World";
std::vector<std::string> results;
// Split the string by commas
boost::split(results, text, boost::is_any_of(","));
for (const auto& word : results) {
std::cout << word << '\n';
}
return 0;
}
Qt
Qt 主要以其 GUI 功能而闻名。然而,跨平台应用程序框架远远超出了窗口和小部件的范围。它支持开发专业桌面应用程序、直观的事件驱动通信、广泛的小部件和库(例如,网络、XML 解析、SQL 数据库交互)等。
Qt 可以部署在 Windows、macOS、Linux 和移动设备上。它鼓励围绕信号和插槽构建干净的架构,并包括文档工具、代码生成器和其他解决方案。
对于较小的项目,Qt 可能过于复杂,因为它的学习曲线很陡峭。它还需要商业许可证,尽管开源版本与有限的简单小部件一起提供,其中软件需要与 GPLv3 许可证公开共享。
基本用法代码段
下面的简单 Qt 示例将创建并显示一个带有按钮和文本“Hello, World!Qt 为你处理大部分内存管理,无需原始指针:
#include <QApplication>
#include <QPushButton>
int main(int argc, char* argv[]) {
QApplication app(argc, argv);
QPushButton button("Hello, World!");
button.resize(200, 60);
button.show();
return app.exec();
}
简单的 DirectMedia 层 (SDL)
SDL 专注于多媒体和游戏开发,提供低级窗口访问;2D 渲染;录音和回放;以及键盘、鼠标和控制器等设备的输入处理。SDL 轻量级且便携,这使其成为在桌面、移动和嵌入式系统上部署的有吸引力的选择。
由于其简单的 2D 绘图和事件处理方法,SDL 被广泛认为是小型游戏或原型的理想选择。然而,它对 3D 图形的支持有限,因此当需要高级 3D 渲染时,它通常与 OpenGL 或 Vulkan 等渲染 API 一起使用。
另一个考虑因素是,SDL 的低级方法可能需要开发人员在构建完整游戏引擎时进行额外的手动工作。
基本用法代码段
此 SDL2 代码初始化窗口和渲染器,进入一个循环来处理事件(允许用户关闭窗口),连续将屏幕清除为黑色,然后在退出时清理资源:
#include <SDL2/SDL.h>
#include <stdbool.h>
int main() {
SDL_Init(SDL_INIT_VIDEO);
SDL_Window *win = SDL_CreateWindow("SDL Example", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 600, SDL_WINDOW_SHOWN);
SDL_Renderer *ren = SDL_CreateRenderer(win, -1, SDL_RENDERER_ACCELERATED);
bool running = true;
SDL_Event e;
while (running) {
while (SDL_PollEvent(&e)) if (e.type == SDL_QUIT) running = false;
SDL_SetRenderDrawColor(ren, 0, 0, 0, 255);
SDL_RenderClear(ren);
SDL_RenderPresent(ren);
}
SDL_DestroyRenderer(ren);
SDL_DestroyWindow(win);
SDL_Quit();
return 0;
}
Asynchronous I/O (ASIO)
ASIO 提供了一个强大的异步 I/O 模型,主要用于构建可扩展的网络应用程序,其中必须有效地处理许多并发连接。它作为独立库和 Boost(作为 Boost.Asio)提供,支持同步和异步作,以及计时器和其他实用程序,使其足够灵活,可以执行各种以网络为中心的任务。
ASIO 的非阻塞架构避免了每个连接依赖单个线程,使服务器能够并发处理更多连接并更有效地使用系统资源。
尽管有这些优点,但 ASIO 对异步编程模式的依赖可能很难掌握,并且广泛的回调链(除非使用协程)可能会导致代码在逻辑流中感觉不那么线性。
基本用法代码段
以下示例同步使用 ASIO,说明如何在没有原始指针的情况下解析连接以及发送和接收数据:
#include <asio.hpp>
#include <iostream>
#include <string>
#include <vector>
int main() {
try {
asio::io_context ioContext;
// Resolve server endpoint
asio::ip::tcp::resolver resolver(ioContext);
auto endpoints = resolver.resolve("127.0.0.1", "8080");
// Create and connect the socket
asio::ip::tcp::socket socket(ioContext);
asio::connect(socket, endpoints);
// Send a message
std::string message = "Hello from client!";
asio::write(socket, asio::buffer(message));
// Receive a response
std::vector<char> reply(1024);
size_t bytes_received = socket.read_some(asio::buffer(reply));
std::cout << "Received: "
<< std::string(reply.begin(), reply.begin() + bytes_received)
<< std::endl;
} catch (const std::exception& e) {
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
TensorFlow C++ API
TensorFlow 的 C++ 接口使程序员能够将复杂的机器学习功能直接嵌入到 C++ 软件中,从而在执行过程中无需 Python。这对于高性能场景(例如 CPU 或 GPU 上的实时推理)以及无法托管完整 Python 环境的边缘设备特别有利。
TensorFlow C++ 支持大规模张量运算,并提供对 Python 提供的大多数功能的访问,包括各种神经网络组件和高效执行内核。
初始设置和构建过程可能很麻烦,特别是如果你必须从源代码编译 TensorFlow,并且 C++ 接口通常级别较低,这意味着与 Python 相比,它有更冗长的代码。然而,性能提升和在本地运行推理的能力可以证明生产环境中的这些复杂性是合理的。
基本用法代码段
此 C++ 代码加载预训练的 TensorFlow 模型,对输入数据运行推理,并检索输出,非常适合没有 Python 的高性能应用程序:
#include <tensorflow/core/public/session.h>
#include <tensorflow/core/platform/env.h>
int main() {
tensorflow::Session* session;
tensorflow::SessionOptions options;
tensorflow::NewSession(options, &session);
tensorflow::GraphDef graph;
tensorflow::ReadBinaryProto(tensorflow::Env::Default(), "model.pb", &graph);
session->Create(graph);
tensorflow::Tensor input(tensorflow::DT_FLOAT, tensorflow::TensorShape({1, 3}));
input.flat<float>() = {1.0, 2.0, 3.0}; // Example input
std::vector<std::pair<std::string, tensorflow::Tensor>> inputs = {{"input_node", input}};
std::vector<tensorflow::Tensor> outputs;
session->Run(inputs, {"output_node"}, {}, &outputs);
std::cout << "Output: " << outputs[0].flat<float>()(0) << std::endl;
session->Close();
return 0;
}
SQLite
SQLite 是一个轻量级的嵌入式 SQL 数据库引擎,在单个文件中实现,这使得它对于中小型数据存储场景来说极为方便。它不需要独立的服务器进程,因此开发人员只需打开文件并使用标准 SQL 语法与数据库交互即可。
这种简单性与 ACID 合规性相结合,使 SQLite 对于桌面或移动应用程序中的本地存储等用例非常可靠。它最适合设备上的数据库、原型设计以及任何需要最小配置和占用空间的场景。
尽管 SQLite 支持事务、触发器和许多其他 SQL 功能,但它不适合繁重的多用户写入负载或大型分布式系统。
基本用法代码段
以下代码段创建一个名为“test.db”的 SQLite 数据库。它生成一个带有 id 和 name 列的用户表,插入两个用户(Alice 和 Bob),并将他们的记录打印到控制台:
#include <sqlite3.h>
#include <stdio.h>
int main() {
sqlite3 *db;
char *errMsg;
sqlite3_open("test.db", &db);
// Create table and insert data
sqlite3_exec(db, "CREATE TABLE IF NOT EXISTS users (id INTEGER, name TEXT);", 0, 0, 0);
sqlite3_exec(db, "INSERT INTO users VALUES (1, 'Alice'), (2, 'Bob');", 0, 0, 0);
// Retrieve and print data
sqlite3_exec(db, "SELECT * FROM users;",
[](void*, int cols, char** data, char**) -> int {
printf("%s: %s\n", data[0], data[1]); return 0;
}, 0, &errMsg);
sqlite3_close(db);
return 0;
}
GoogleTest (GTest)
Google Test 因其可靠性、社区支持和易于集成而脱颖而出,成为一种流行且全面的 C++ 测试框架。它提供了一组丰富的断言(例如,EXPECT_EQ、ASSERT_FALSE 等),并支持测试夹具以实现清晰的设置和拆卸代码。
通过从一开始就将 Google Test 集成到项目中,开发人员可以保持高代码质量并及时检测回归。其控制台驱动的界面在持续集成环境中无缝运行。
尽管它为更高级的场景提供了单独的 Google Mock 库,但模拟功能可能需要对框架有更深入的了解。
基本用法代码段
以下代码演示了如何编写和运行 Google 测试:
#include <gtest/gtest.h>
int Add(int a, int b) {
return a + b;
}
TEST(MathTest, AddFunction) {
EXPECT_EQ(Add(1, 1), 2);
EXPECT_EQ(Add(-1, 2), 1);
}
int main(int argc, char** argv) {
::testing::InitGoogleTest(&argc, argv);
return RUN_ALL_TESTS();
}
英特尔线程构建块 (oneTBB)
英特尔 TBB 是一个功能强大的并行库,可以代表开发人员处理低级线程细节,使他们能够以更具声明性的风格编写任务。通过采用基于任务的方法,TBB 可以有效地在多个线程之间调度和分配工作负载,使其非常适合涉及数据处理、模拟和其他 CPU 密集型任务的应用程序。
它对并发容器和并行算法(例如 parallel_for 和 parallel_reduce)的支持有助于简化常见的并行计算模式。
但是,TBB 可能会增加小规模项目的开销,如果你来自直接线程管理,则需要时间来适应基于任务的思维方式。
基本用法代码段
此代码片段显示如何使用具有阻塞范围的 Intel TBB parallel_for 并行递增 std::vector 的元素:
#include <tbb/parallel_for.h>
#include <tbb/blocked_range.h>
#include <iostream>
#include <vector>
int main() {
std::vector<int> data(100000, 1);
tbb::parallel_for(
tbb::blocked_range<size_t>(0, data.size()),
[&](const tbb::blocked_range<size_t>& range) {
for (size_t i = range.begin(); i < range.end(); ++i) {
data[i] += 1;
}
}
);
std::cout << "First element after parallel increment: " << data[0] << std::endl;
return 0;
}
Eigen
Eigen 是一个仅标头的 C++ 库,面向线性代数计算,为矩阵、向量和相关运算(例如分解(LU、QR、SVD)和几何变换(例如四元数和旋转))提供高效的实现。
它利用表达式模板来优化链式作,通常会产生可与手动调整的代码相媲美的性能。
这种复杂程度可能会导致编译时间更长,特别是当模板和内联变得广泛时。然而,Eigen 易于集成、支持固定和动态大小矩阵以及全面的功能集使其成为从机器人和计算机视觉到模拟和科学研究等任务的首选解决方案。
基本用法代码段
以下是使用特征进行乘法和反演的特征矩阵运算的示例演示×22矩阵和向量:
#include <Eigen/Dense>
#include <iostream>
int main() {
Eigen::Matrix2d mat;
mat << 1, 2,
3, 4;
Eigen::Vector2d vec(5, 6);
Eigen::Vector2d result = mat * vec;
std::cout << "Result:\n" << result << std::endl;
Eigen::Matrix2d inv = mat.inverse();
std::cout << "Inverse:\n" << inv << std::endl;
return 0;
}
PortAudio
PortAudio 是一个免费的跨平台库,用于实时音频 I/O。虽然是用 C 语言编写的,但它与 C++ 项目完美集成,让开发人员可以以最小的延迟录制和播放音频流。其简单的特性使 PortAudio 成为那些在数字音频工作站、语音聊天客户端或音乐制作工具等应用中需要原始音频 I/O 的人的有力候选者。
PortAudio 本身不包含更高级别的效果或 DSP 功能,因此开发人员在需要高级音频处理时经常将其与其他库集成。
基本用法代码段
以下代码片段演示了如何通过 C++ 中的 RAII 处理 PortAudio 流:
#include <portaudio.h>
#include <iostream>
#define SAMPLE_RATE 44100
#define FRAMES_PER_BUFFER 512
static int audioCallback(const void* input, void* output, unsigned long frames,
const PaStreamCallbackTimeInfo*, PaStreamCallbackFlags, void*) {
memcpy(output, input, frames * sizeof(float) * 2); // Simple loopback (stereo)
return paContinue;
}
int main() {
Pa_Initialize();
PaStream* stream;
Pa_OpenDefaultStream(&stream, 2, 2, paFloat32, SAMPLE_RATE, FRAMES_PER_BUFFER, audioCallback, nullptr);
Pa_StartStream(stream);
std::cout << "Loopback running... Press Enter to stop.\n";
std::cin.get();
Pa_StopStream(stream);
Pa_CloseStream(stream);
Pa_Terminate();
return 0;
}
Incredibuild:加快 C++项目的构建速度
无论你选择哪种库,Incredibuild 都是一种构建加速工具(不是库),它可以通过在多台机器上分配编译任务来大幅缩短大型 C++项目中的编译时间。
Incredibuild 的主要特点包括:
- 分布式构建 :利用网络中的空闲 CPU 内核
- 缓存机制 :避免重新编译未更改的文件
- 集成 :适用于许多构建系统(Visual Studio、Make 等)
Incredibuild 的其他优势包括:
- 节省大量时间 :对于大型企业代码库保持快速 CI 周期至关重要
- 可扩展性 :机器越多,构建速度越快
- 易用性:只需对现有构建配置进行最少的更改
集成 C++ 库的最佳实践
让我们回顾一下集成 C++ 库时需要考虑的一些重要方面:
- 根据项目需求评估库: 首先确定你的瓶颈和功能要求;例如,如果你需要轻量级嵌入式数据库,请使用 SQLite,如果你的工作负载具有高度可并行化,请使用 Intel TBB。
- 注意你的依赖关系: 选择 vcpkg、Conan 或 Hunter 等包管理器来简化集成;保持清晰的版本控制策略,让每个人都保持同步。
- 优化构建时间: 使用 Incredibuild 等工具来加速大型项目;利用增量构建、缓存和持续集成管道。
- 采用现代 C++ 功能: 利用引用、智能指针(std::unique_ptr、std::shared_ptr)和 RAII 安全地管理资源;利用 std::filesystem (C++17) 或协程 (C++20) 等功能来替换旧方法。
- 尽早接受测试: 从项目开始就集成 Google Test 或其他测试框架;使用持续集成 (CI) 自动执行测试运行,以快速反馈代码更改。
- 记录和模块化: 为每个外部库的使用提供清晰的文档;将库依赖项隔离在单独的模块中以包含复杂性。
结论
大中型组织中的 C++ 开发人员不断面临构建可扩展、可维护和高性能软件的挑战。通过仔细选择库,你可以显着缩短开发时间,同时确保代码质量保持较高。此外,请确保采用现代 C++ 最佳实践,例如最小化原始指针、强调引用和 RAII、彻底测试以及利用分布式构建。
最终,这些库不仅仅是工具;它们是创新的催化剂,使你能够专注于创造价值,而不是陷入低级细节的泥潭。
通过不断完善你的技术和探索新功能,你可以突破 C++ 所能实现的界限。要了解 Incredibuild 如何帮助你加速 C++项目,请立即注册免费试用。