C++ HTTP客户端库cpr:现代化网络请求的实践指南

发布时间:2026/7/14 6:33:05
C++ HTTP客户端库cpr:现代化网络请求的实践指南 1. 项目概述为什么我们需要一个现代的C HTTP客户端库如果你用C写过需要和网络打交道的程序比如从某个API拉取数据、上传文件到服务器或者实现一个简单的爬虫那你大概率经历过一个痛苦的阶段要么是吭哧吭哧地用系统底层的socket API处理一堆连接、超时、编码的琐事要么是去集成一个庞大而古老的库比如libcurl然后被它那C风格的回调函数和复杂的选项配置搞得头晕眼花。我自己在早期项目里就没少踩这些坑直到后来发现了cpr这个库才感觉在C里做HTTP请求终于能像在Python里用requests那样优雅和直接了。cpr全称是“C Requests”它的设计哲学非常明确为C开发者提供一个简单、直观、现代化且功能强大的HTTP客户端库。它的API设计深受Python的requests库启发所以如果你熟悉requests那么上手cpr几乎没有任何障碍。它的核心价值在于把开发者从繁琐的底层网络细节和复杂的配置中解放出来让你能更专注于业务逻辑本身。无论是快速原型开发还是需要高性能、高可靠性的生产环境cpr都能很好地胜任。这个库适合所有层次的C开发者。对于新手它友好的API能让你在几分钟内就发出第一个HTTP请求快速获得正反馈。对于有经验的开发者它提供的异步支持、连接池、代理、Cookie管理等高级特性足以应对复杂的网络交互场景。接下来我就结合自己多年的使用经验带你从零开始彻底掌握这个能极大提升你开发效率的工具。2. cpr的核心优势与设计哲学2.1 告别libcurl的“旧时代”cpr带来的改变在cpr出现之前libcurl几乎是C/C领域进行HTTP通信的事实标准。libcurl非常强大但也非常“C”。这意味着你需要处理大量的回调函数、手动管理内存设置CURLOPT_WRITEFUNCTION和CURLOPT_WRITEDATA、以及面对一长串令人望而生畏的CURLOPT_选项。代码的可读性和可维护性常常因此大打折扣。cpr在底层实际上封装了libcurl但它做了一次漂亮的“面向对象”和“现代化”的改造。它把libcurl那些过程式的、基于回调的接口包装成了基于RAII资源获取即初始化和异常安全可选的C风格API。举个例子在libcurl里下载一个文件并保存到内存你需要写一个回调函数来拼接数据块而在cpr里你只需要一行代码cpr::Response r cpr::Get(cpr::Url{“https://api.example.com/data”});响应内容就已经在r.text里了。这种设计哲学的核心是“约定优于配置”和“显式优于隐式”。库为你设定了合理的默认行为比如自动处理重定向、设置常见的User-Agent同时通过清晰的命名和链式调用来设置参数让代码意图一目了然。2.2 关键特性一览不止于GET和POST很多人初看cpr觉得它就是个发GET和POST请求的库。这其实低估了它的能力。经过多个版本的迭代cpr已经成为一个功能相当全面的HTTP客户端。以下是它的一些核心特性我将在后续章节详细展开全面的HTTP方法支持 基础的GET, POST, PUT, DELETE, HEAD, OPTIONS自然不在话下。灵活的请求参数设置URL参数 通过cpr::Parameters以键值对形式方便地构建查询字符串。请求体 支持多种格式包括纯文本(cpr::Body)、表单数据(cpr::Payload或cpr::Multipart)、JSON字符串。请求头 通过cpr::Header轻松设置任何自定义头部。认证与代理 支持Basic Auth、Bearer Token认证以及HTTP/HTTPS/SOCKS5代理。超时与重试 可以设置连接超时、请求超时并内置了简单的重试机制。Cookie管理 自动处理Cookie的发送和存储也支持手动管理Cookie Jar。文件上传与下载 通过cpr::Multipart支持文件上传下载大文件时可以使用回调函数流式处理避免内存爆掉。异步请求支持 这是cpr的杀手锏之一。它提供了基于std::future的异步接口让你能轻松实现非阻塞的并发HTTP请求极大提升程序性能。SSL/TLS支持 底层依赖libcurl的SSL后端支持HTTPS请求可以指定CA证书路径或跳过证书验证仅用于测试。连接池 对于需要频繁向同一主机发起请求的场景连接池可以复用TCP连接减少握手开销提升性能。这些特性使得cpr不仅能用于简单的REST API调用也能处理像模拟登录、爬虫、文件传输服务等更复杂的网络任务。3. 从零开始cpr的安装与项目配置3.1 跨平台安装指南cpr是一个纯头文件库吗不完全是。它依赖libcurl所以你需要先确保系统里安装了libcurl的开发文件。它的安装方式非常灵活适合各种构建系统。在Linux (Ubuntu/Debian) 上# 1. 安装依赖库libcurl sudo apt-get update sudo apt-get install libcurl4-openssl-dev # 2. 安装cpr。推荐使用包管理器vcpkg或从源码构建。 # 方法A: 使用vcpkg (推荐方便管理) git clone https://github.com/Microsoft/vcpkg.git cd vcpkg ./bootstrap-vcpkg.sh # Linux ./vcpkg integrate install ./vcpkg install cpr # 方法B: 从源码安装 git clone https://github.com/libcpr/cpr.git cd cpr mkdir build cd build cmake .. -DCPR_USE_SYSTEM_CURLON # 使用系统已安装的curl make sudo make install在macOS 上# 使用Homebrew是最简单的方式 brew install cpr # 或者同样可以使用vcpkg或源码编译。在Windows 上Windows上的配置稍复杂因为缺乏统一的包管理器。我强烈推荐使用vcpkg它能自动处理依赖和编译。安装vcpkg (如果尚未安装):git clone https://github.com/Microsoft/vcpkg.git cd vcpkg .\bootstrap-vcpkg.bat .\vcpkg integrate install使用vcpkg安装cpr:.\vcpkg install cpr:x64-windows # 安装64位版本 # 或者 .\vcpkg install cpr:x86-windows 安装32位版本这会自动下载并编译cpr及其依赖libcurl。3.2 CMake项目集成现代C项目标配现在绝大多数C项目都用CMake管理cpr对此有很好的支持。假设你的项目结构如下my_project/ ├── CMakeLists.txt ├── src/ │ └── main.cpp └── ...如果你用系统包管理器或源码安装了cpr在你的CMakeLists.txt中使用find_package来定位cpr。cmake_minimum_required(VERSION 3.16) project(my_http_client) set(CMAKE_CXX_STANDARD 17) # cpr需要C11及以上推荐17 # 查找cpr库 find_package(cpr REQUIRED) add_executable(${PROJECT_NAME} src/main.cpp) # 将cpr::cpr链接到你的目标 target_link_libraries(${PROJECT_NAME} PRIVATE cpr::cpr)如果你使用vcpkg在CMake配置时需要指定vcpkg的工具链文件。# 在项目根目录my_project/下执行 cmake -B build -S . -DCMAKE_TOOLCHAIN_FILE[path_to_vcpkg]/scripts/buildsystems/vcpkg.cmake cmake --build build你的CMakeLists.txt写法同上find_package命令会被vcpkg自动处理。注意一个常见的坑是在Windows上使用Visual Studio如果你通过其他方式安装了libcurl比如自己编译可能会遇到链接错误提示找不到__imp_curl_easy_init之类的符号。这通常是因为编译cpr时使用的libcurl库和你项目链接的库不匹配Debug/Release、动态库/静态库。最稳妥的方案就是让cpr和你的项目使用同一份libcurl这也是为什么vcpkg方案最省心——它保证了依赖的一致性。3.3 验证安装你的第一个cpr程序创建一个简单的main.cpp文件来测试安装是否成功#include iostream #include cpr/cpr.h // 包含cpr头文件 int main() { // 发起一个简单的GET请求到公共测试API cpr::Response r cpr::Get(cpr::Url{https://httpbin.org/get}); // 打印HTTP状态码 std::cout Status code: r.status_code std::endl; // 应该输出 200 // 打印响应体JSON文本 std::cout Response text:\n r.text std::endl; // 检查请求是否成功状态码2xx if (r.status_code 200) { std::cout Request succeeded! std::endl; } else { std::cout Request failed! std::endl; } return 0; }编译并运行这个程序记得链接cpr库。如果看到返回的JSON数据和状态码200那么恭喜你cpr环境已经配置成功4. cpr API深度解析与实战应用4.1 同步请求基础但不可或缺同步请求是阻塞式的调用cpr::Get等函数后程序会等待直到收到HTTP响应或超时。它逻辑简单适用于顺序执行、快速脚本或对并发要求不高的场景。4.1.1 GET请求与参数传递GET请求通常用于获取资源参数附在URL后面。#include cpr/cpr.h #include iostream int main() { // 方法1直接将参数拼接在URL里不推荐需要自己处理URL编码 // auto r cpr::Get(cpr::Url{https://httpbin.org/get?namefoovalue1}); // 方法2使用cpr::Parameters库会自动处理URL编码 auto r cpr::Get(cpr::Url{https://httpbin.org/get}, cpr::Parameters{{name, foo}, {value, 1}, {city, New York}}); std::cout r.url std::endl; // 输出完整的URL std::cout r.text std::endl; return 0; }cpr::Parameters是一个std::initializer_liststd::pairstd::string, std::string用起来非常直观。它会自动将特殊字符如空格、中文进行百分比编码。4.1.2 POST请求与多种数据格式POST请求用于提交数据cpr支持多种数据格式。发送表单数据 (application/x-www-form-urlencoded):这是网页表单默认的提交格式。auto r cpr::Post(cpr::Url{https://httpbin.org/post}, cpr::Payload{{username, myuser}, {password, mypass}}); // cpr::Payload 的用法和 cpr::Parameters 类似但数据放在请求体中。发送JSON数据 (application/json):这是现代API最常用的格式。#include nlohmann/json.hpp // 推荐使用nlohmann/json库来处理JSON using json nlohmann::json; json data; data[title] Hello; data[body] World; data[userId] 1; auto r cpr::Post(cpr::Url{https://jsonplaceholder.typicode.com/posts}, cpr::Header{{Content-Type, application/json}}, cpr::Body{data.dump()}); // 将json对象序列化为字符串 std::cout r.text std::endl;实操心得在实际项目中我强烈建议使用像nlohmann/json这样的库来构建和解析JSON而不是手动拼接字符串。它能避免很多转义错误并且代码更清晰。记得设置正确的Content-Type头否则服务器可能无法正确解析。发送Multipart表单数据 (multipart/form-data):常用于文件上传。// 上传一个文本字段和一个文件 auto r cpr::Post(cpr::Url{https://httpbin.org/post}, cpr::Multipart{{description, This is a test file}, {file, cpr::File{../test.jpg}}});cpr::File构造函数接受文件路径。cpr会读取文件内容并将其作为multipart的一部分发送。4.1.3 处理响应cpr::Response对象包含了服务器返回的所有信息status_code: HTTP状态码200, 404, 500等。text: 响应体以std::string形式存储。对于文本内容HTML, JSON, XML很方便。header: 响应头是一个std::mapstd::string, std::string。url: 最终请求的URL考虑重定向后。elapsed: 请求耗时。cookies: 服务器设置的Cookies。error: 如果请求过程中发生错误如网络断开这里会包含错误信息。auto r cpr::Get(cpr::Url{https://api.github.com}); if (r.error) { std::cerr Request error: r.error.message std::endl; } else { std::cout Status: r.status_code std::endl; std::cout Content-Type: r.header[content-type] std::endl; // 解析JSON响应 // auto json_response nlohmann::json::parse(r.text); }4.2 异步请求提升性能的利器当你的应用需要同时发起多个网络请求或者不想因为一个慢速的网络调用而阻塞整个线程时异步请求就是最佳选择。cpr的异步API返回一个std::futurecpr::Response。4.2.1 基本异步调用#include cpr/cpr.h #include future #include vector #include iostream int main() { // 发起一个异步GET请求 std::futurecpr::Response future_response cpr::GetAsync(cpr::Url{https://httpbin.org/delay/2}); // 这个端点会延迟2秒响应 std::cout Request sent, doing other work... std::endl; // ... 这里可以执行其他不依赖响应的任务 ... // 当需要结果时调用future.get()这会阻塞直到响应就绪 cpr::Response r future_response.get(); std::cout Async request completed with status: r.status_code std::endl; return 0; }4.2.2 实现并发请求异步的真正威力在于并发。我们可以同时发起多个请求然后一起等待它们完成。std::vectorstd::string urls { https://httpbin.org/delay/1, https://httpbin.org/delay/2, https://httpbin.org/delay/3 }; std::vectorstd::futurecpr::Response responses; for (const auto url : urls) { responses.push_back(cpr::GetAsync(cpr::Url{url})); } std::cout All requests fired. Waiting... std::endl; // 遍历future向量获取结果。总耗时约等于最慢的那个请求~3秒而非顺序执行的6秒。 for (auto future_resp : responses) { cpr::Response r future_resp.get(); // 按完成顺序获取不一定是发起顺序 std::cout URL: r.url - Status: r.status_code , Time: r.elapsed ms\n; }注意事项std::future::get()方法只能调用一次调用后future就变为无效。如果你需要在多个地方使用结果需要保存cpr::Response对象。另外大量并发请求可能会对目标服务器造成压力也可能耗尽本地文件描述符在生产环境中需要合理控制并发度并考虑使用连接池。4.3 高级配置应对复杂网络环境真实的网络环境很少有一帆风顺的cpr提供了丰富的选项来应对各种情况。4.3.1 超时与重试网络不稳定时超时设置是必须的。// 设置连接超时(3秒)和请求总超时(10秒) auto r cpr::Get(cpr::Url{https://httpbin.org/delay/5}, cpr::Timeout{10000}, // 总超时10秒 cpr::ConnectTimeout{3000}); // 连接超时3秒 // 启用重试机制最多重试3次每次重试间隔逐渐增加指数退避 cpr::Session session; session.SetUrl(cpr::Url{https://unstable-api.example.com}); session.SetTimeout(cpr::Timeout{5000}); session.SetRetry(cpr::RetryWithDelay(3, std::chrono::milliseconds(100))); // 重试3次基础间隔100ms auto response session.Get();cpr::Session对象提供了更精细的控制适合需要复用配置的复杂请求场景。4.3.2 代理与认证在公司内网或需要特定网络路由时代理和认证是关键。// 使用HTTP代理 auto r1 cpr::Get(cpr::Url{https://httpbin.org/ip}, cpr::Proxies{{http, http://10.10.1.10:8080}, {https, http://10.10.1.10:8080}}); // 注意https代理的协议 // 使用代理并携带认证信息 auto r2 cpr::Get(cpr::Url{https://httpbin.org/ip}, cpr::Proxies{{https, http://proxy.com:3128}}, cpr::ProxyAuthentication{{http, cpr::EncodedAuthentication{user, pass}}}); // API端点认证 (Bearer Token / Basic Auth) // Bearer Token (常用于JWT) auto r3 cpr::Get(cpr::Url{https://api.secure.com/data}, cpr::Bearer{your_jwt_token_here}); // Basic Auth auto r4 cpr::Get(cpr::Url{https://httpbin.org/basic-auth/user/passwd}, cpr::Authentication{user, passwd}); // cpr会自动进行Base64编码4.3.3 Cookie管理与会话保持cpr可以自动处理Cookie模拟浏览器会话。cpr::Session session; session.SetUrl(cpr::Url{https://httpbin.org/cookies/set?sessionidabc123}); session.SetCookies(cpr::Cookies{{preference, chocolate}}); // 设置初始Cookie auto r1 session.Get(); // 这次请求会发送我们设置的Cookie并接收服务器设置的Cookie std::cout Response1: r1.text std::endl; // 同一个session对象发起第二次请求会自动携带上次请求收到的Cookiesessionid session.SetUrl(cpr::Url{https://httpbin.org/cookies}); auto r2 session.Get(); std::cout Response2: r2.text std::endl; // 会显示 {cookies: {sessionid: abc123, preference: chocolate}}使用cpr::Session对象是管理有状态会话如登录后的一系列操作的最佳实践。5. 性能优化与最佳实践5.1 连接池复用连接以提升效率HTTP/1.1默认支持持久连接Keep-Alive但如果你每次请求都创建一个新的cpr::Session或直接调用cpr::Get底层可能会频繁地建立和断开TCP连接造成不必要的开销。对于需要向同一主机发起大量请求的场景启用连接池能带来显著的性能提升。cpr的连接池功能是通过cpr::Session和libcurl的底层特性共同实现的。核心在于复用同一个cpr::Session对象。cpr::Session session; session.SetUrl(cpr::Url{https://api.example.com}); // 为这个session设置一些通用参数如超时、头部等 session.SetTimeout(cpr::Timeout{5000}); session.SetHeader(cpr::Header{{User-Agent, MyApp/1.0}}); // 第一次请求会建立TCP连接 auto resp1 session.Get(); // 紧接着的第二次请求可能会复用已有的连接如果服务器支持Keep-Alive session.SetParameters(cpr::Parameters{{page, 2}}); auto resp2 session.Get();libcurl内部会为每个CURL*句柄对应一个cpr::Session维护一个连接缓存。当使用同一个session对象向同一个主机相同协议、主机名、端口发起请求时它会尝试复用现有的空闲连接。性能对比心得在一个内部监控项目中我需要每分钟轮询几十个服务的健康端点。最初我为每个请求创建临时对象CPU和网络TIME_WAIT状态连接数都很高。改为使用一个全局的cpr::Session对象池后平均响应时间下降了约40%服务器负载也明显降低。对于高频请求连接池是必选项。5.2 流式下载与上传处理大文件默认情况下cpr会将整个响应体读入内存的std::string中。对于下载一个大文件比如几百MB的电影这会导致内存瞬间暴涨。cpr提供了写入回调WriteCallback来支持流式处理。#include fstream #include cpr/cpr.h // 自定义一个写入回调将数据直接写入文件 size_t writeCallback(char* ptr, size_t size, size_t nmemb, void* userdata) { std::ofstream* file static_caststd::ofstream*(userdata); size_t totalSize size * nmemb; file-write(ptr, totalSize); return totalSize; // 必须返回实际处理的字节数否则libcurl会认为出错 } int main() { std::ofstream outfile(large_file.zip, std::ios::binary); if (!outfile.is_open()) { std::cerr Failed to open file for writing.\n; return 1; } cpr::Session session; session.SetUrl(cpr::Url{https://example.com/large_file.zip}); session.SetWriteCallback(cpr::WriteCallback(writeCallback, outfile)); // 设置回调函数和用户数据文件流指针 // 也可以设置进度回调来显示下载进度 // session.SetProgressCallback(cpr::ProgressCallback([](...){ return true; })); auto r session.Get(); outfile.close(); if (r.status_code 200) { std::cout File downloaded successfully.\n; } else { std::cerr Download failed with status: r.status_code std::endl; } return 0; }同理你也可以使用ReadCallback来实现流式上传大文件避免一次性将整个文件读入内存。5.3 错误处理与日志调试健壮的程序必须妥善处理错误。cpr的错误主要来自两个方面网络/系统错误和HTTP协议错误。cpr::Response r cpr::Get(cpr::Url{https://non-existent-domain-xyz.com}); // 1. 检查底层网络错误 if (r.error.code ! cpr::ErrorCode::OK) { std::cerr Network error: r.error.message std::endl; // 可能的原因域名无法解析、连接被拒绝、超时等 return; } // 2. 检查HTTP状态码 if (r.status_code 400) { std::cerr HTTP error: r.status_code std::endl; std::cerr Response body: r.text std::endl; // 服务器可能返回了错误详情 // 处理不同的状态码404 Not Found, 500 Internal Server Error, 403 Forbidden等 switch(r.status_code) { case 404: /* 处理未找到 */ break; case 429: /* 处理请求过多需要降速 */ break; case 500: /* 处理服务器内部错误 */ break; default: break; } } // 3. 启用详细日志调试时非常有用 // 设置环境变量 CPR_LOG_LEVELdebug 或者在你的代码中设置如果cpr编译时启用了日志 // 日志会输出到stderr包含详细的HTTP请求和响应信息有助于排查问题。对于调试复杂的请求我经常使用cpr::Session配合cpr::Verbose选项或者使用像Postman或curl命令先验证API本身是否工作正常再在代码中复现。有时候问题不在代码而在服务器配置或网络策略上。6. 常见问题排查与实战技巧即使对库很熟悉在实际开发中还是会遇到各种稀奇古怪的问题。这里我总结了一份“避坑指南”。6.1 编译与链接问题速查表问题现象可能原因解决方案编译错误undefined reference tocpr::Get(...)没有正确链接cpr库确保CMake的target_link_libraries包含了cpr::cpr。检查库路径。链接错误undefined reference to curl_easy_...没有链接libcurl或链接的版本不匹配cpr依赖libcurl。确保安装了libcurl-dev(el)包并且CMake能找到它。在Windows上使用vcpkg可避免此问题。运行时崩溃Windows MSVCDebug/Release库混用确保你的项目构建配置Debug/Release与链接的cpr/libcurl库的配置一致。vcpkg默认安装Release版如果你在Debug模式下开发需要安装cpr:x64-windows-static-md或指定三重态。SSL certificate problem: unable to get local issuer certificatelibcurl找不到CA证书包来验证HTTPS证书Linux/macOS:安装ca-certificates包。Windows:将cURL的CA证书包(curl-ca-bundle.crt)放在合适位置或通过cpr::SslOptions设置路径或仅测试环境使用cpr::VerifySsl(false)跳过验证不安全。6.2 运行时典型问题与解决问题1请求非常慢或者偶尔超时。排查思路DNS解析可能是DNS服务器慢或不稳定。尝试使用cpr::Resolve指定IP或检查系统的DNS配置。连接池是否频繁创建销毁Session复用Session对象。超时设置默认超时可能很长。根据场景合理设置cpr::Timeout和cpr::ConnectTimeout。服务器限流检查是否返回429状态码。需要在代码中实现退避重试逻辑。代理问题如果身处公司网络可能需要配置代理但代理本身可能成为瓶颈。问题2发送中文或特殊字符出现乱码。原因与解决这通常是字符编码问题。cpr的text是std::string存储的是原始字节。URL参数使用cpr::Parameterscpr会自动进行URL编码。请求体/响应体确保你和服务器对文本编码的认知一致如UTF-8。对于响应你可能需要将std::string转换为std::wstring或使用如iconv这样的库进行转码。如果响应是JSON使用JSON库如nlohmann/json解析它通常能处理好编码。问题3异步请求似乎没有真正并发。原因cpr::GetAsync只是将任务提交到libcurl的异步队列但默认情况下所有的异步请求共享同一个libcurl的“多句柄”multi handle并在发起get()的线程中执行。如果你在单线程中顺序调用future.get()它们仍然是顺序完成的。解决方案要实现真正的并行你需要将std::future对象放到不同的线程中去等待(get())。或者更高级的做法是为不同的任务组使用不同的cpr::AsyncSession如果cpr版本支持或者结合像libuv、Boost.Asio这样的事件循环库来驱动libcurl的多句柄。问题4内存泄漏报告。排查cpr本身基于RAII一般不会泄漏。问题可能出在回调函数异常自定义的WriteCallback/ReadCallback如果抛出异常且未被libcurl捕获可能导致资源未释放。确保回调函数是异常安全的。全局对象析构顺序如果在全局或静态对象中使用cpr程序退出时这些对象析构时若cpr底层libcurl的全局资源已释放可能导致问题。尽量避免在全局范围内进行网络操作。6.3 一个综合实战案例构建一个简单的API客户端假设我们要为一个虚构的天气服务API编写客户端。该API需要API Key返回JSON数据。#include cpr/cpr.h #include nlohmann/json.hpp #include iostream #include string class WeatherClient { private: std::string api_key_; std::string base_url_ https://api.weatherapp.com/v1; cpr::Session session_; // 复用Session public: explicit WeatherClient(const std::string api_key) : api_key_(api_key) { // 配置Session的通用参数 session_.SetHeader(cpr::Header{{User-Agent, MyWeatherApp/1.0}, {Accept, application/json}}); session_.SetTimeout(cpr::Timeout{10000}); session_.SetConnectTimeout(cpr::Timeout{3000}); } std::optionalnlohmann::json getCurrentWeather(const std::string city) { session_.SetUrl(cpr::Url{base_url_ /current.json}); session_.SetParameters(cpr::Parameters{{key, api_key_}, {q, city}, {aqi, no}}); // 重置可能之前设置的Body等确保是GET请求 session_.SetBody(cpr::Body{}); try { auto response session_.Get(); if (response.error) { std::cerr Network error: response.error.message std::endl; return std::nullopt; } if (response.status_code ! 200) { std::cerr API error: response.status_code , body: response.text.substr(0, 200) std::endl; return std::nullopt; } return nlohmann::json::parse(response.text); } catch (const nlohmann::json::parse_error e) { std::cerr Failed to parse JSON: e.what() std::endl; return std::nullopt; } } // 可以添加更多方法如getForecast等 }; int main() { WeatherClient client(your_api_key_here); auto weather_data client.getCurrentWeather(London); if (weather_data) { std::cout Temperature in London: (*weather_data)[current][temp_c].getdouble() °C\n; } return 0; }这个案例展示了如何封装一个健壮的、可复用的API客户端包含了错误处理、JSON解析和Session复用等最佳实践。