Ch 16: 最佳实践
- 遵循 C++ 代码规范
- 编写有效的单元测试
- 使用 Doxygen 生成文档
- 实现跨平台开发
- 了解常用工具链
16.1 代码风格与规范
Section titled “16.1 代码风格与规范”16.1.1 命名约定
Section titled “16.1.1 命名约定”| 类型 | 风格 | 示例 |
|---|---|---|
| 变量 | snake_case 或 camelCase | user_name, userName |
| 常量 | kConstantName 或 CONSTANT_NAME | kMaxRetries, MAX_RETRIES |
| 函数 | snake_case 或 camelCase | calculate_total(), calculateTotal() |
| 类 | PascalCase | BankAccount, HttpClient |
| 命名空间 | snake_case | math_utils, http_server |
| 模板参数 | PascalCase 或 snake_case | typename T, typename ValueType |
| 成员变量 | member_ 或 memberName | count_, count |
Google C++ 风格指南的命名规则:
#include <string>#include <vector>
// 类名:PascalCaseclass BankAccount {public: // 构造函数 BankAccount(const std::string& name, double balance) : account_name_(name), balance_(balance) {}
// 公开方法:camelCase std::string GetAccountName() const { return account_name_; } double GetBalance() const { return balance_; }
// 私有成员:_suffixprivate: std::string account_name_; double balance_;};
// 常量:k 前缀const int kMaxConnections = 100;const double kPi = 3.14159265358979;
// 全局变量:g 前缀(尽量避免)int g_global_counter = 0;
// 枚举值:k 前缀或全部大写enum class ErrorCode { kSuccess = 0, kNotFound = 404, kInternalError = 500};
// 命名空间:全小写namespace math_utils { double calculate_area(double radius) { return kPi * radius * radius; }}16.1.2 代码格式化
Section titled “16.1.2 代码格式化”使用 .clang-format 保持代码风格一致:
BasedOnStyle: GoogleIndentWidth: 4ColumnLimit: 100PointerAlignment: LeftReferenceAlignment: Left16.1.3 头文件规范
Section titled “16.1.3 头文件规范”#ifndef PROJECT_FOO_H // 或 #pragma once#define PROJECT_FOO_H
// 1. 先包含项目头文件#include "project/bar.h"
// 2. 再包含依赖库#include <vector>#include <string>
// 3. 前向声明(减少编译依赖)class Baz; // 前向声明
// 4. 避免 using 声明在头文件// using std::string; // 不好
#endif
// foo.cpp#include "foo.h"#include "project/baz.h" // 实际需要的才包含16.2 测试策略
Section titled “16.2 测试策略”16.2.1 测试框架选择
Section titled “16.2.1 测试框架选择”| 框架 | 特点 | 适用场景 |
|---|---|---|
| Google Test | 功能丰富,跨平台 | 大型项目 |
| doctest | 头文件only,轻量 | 快速开发 |
| Catch2 | BDD 风格,语法简洁 | 中小项目 |
| Boost.Test | 功能全面 | Boost 项目 |
16.2.2 Google Test 入门
Section titled “16.2.2 Google Test 入门”#include <gtest/gtest.h>#include "math.h"
// 测试夹具(可选)class MathTest : public ::testing::Test {protected: void SetUp() override { // 每个测试前调用 } void TearDown() override { // 每个测试后调用 }};
// 简单测试TEST(MathTest, Add) { EXPECT_EQ(add(1, 2), 3); EXPECT_EQ(add(-1, 1), 0);}
// 浮点比较(使用近似)TEST(MathTest, Divide) { EXPECT_DOUBLE_EQ(divide(10.0, 3.0), 3.333333); EXPECT_NEAR(divide(10.0, 3.0), 3.333, 0.001);}
// 测试异常TEST(MathTest, DivideByZero) { EXPECT_THROW(divide(1, 0), std::invalid_argument);}
// 测试布尔条件TEST(MathTest, IsEven) { EXPECT_TRUE(is_even(4)); EXPECT_FALSE(is_even(5));}
// 参数化测试class AddTest : public ::testing::TestWithParam<std::tuple<int, int, int>> {};
TEST_P(AddTest, HandlesParameters) { auto [a, b, expected] = GetParam(); EXPECT_EQ(add(a, b), expected);}
INSTANTIATE_TEST_CASE_P( PositiveNumbers, AddTest, ::testing::Values( std::make_tuple(1, 2, 3), std::make_tuple(0, 0, 0), std::make_tuple(-1, 1, 0) ));16.2.3 doctest(轻量级)
Section titled “16.2.3 doctest(轻量级)”#define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN#include <doctest/doctest.h>#include "math.h"
int add(int a, int b) { return a + b; }
TEST_CASE("addition") { CHECK(add(1, 2) == 3); CHECK(add(-1, 1) == 0);}
TEST_CASE("division") { CHECK(divide(10, 2) == 5); CHECK_THROWS(divide(1, 0)); // 测试异常}16.2.4 测试覆盖率
Section titled “16.2.4 测试覆盖率”# 使用 gcov (GCC) 或 lcov 生成覆盖率报告g++ -fprofile-arcs -ftest-coverage -o test test.cpp./testgcov test.cpplcov --capture --directory . --output-file coverage.infogenhtml coverage.info --output-directory coverage16.2.5 测试金字塔
Section titled “16.2.5 测试金字塔” ┌─────────┐ │ E2E │ 少量:端到端测试 │ Tests │ ┌┴─────────┴┐ │ Integration│ 中等:模块集成测试 │ Tests │ ┌┴───────────┴┐ │ Unit │ 大量:单元测试 │ Tests │ └─────────────┘16.3 文档生成
Section titled “16.3 文档生成”16.3.1 Doxygen 配置
Section titled “16.3.1 Doxygen 配置”# Doxyfile 基础配置PROJECT_NAME = "My Project"INPUT = src/OUTPUT_DIRECTORY = docs/RECURSIVE = YESHAVE_DOT = YESGENERATE_HTML = YESGENERATE_LATEX = YES16.3.2 文档注释格式
Section titled “16.3.2 文档注释格式”/** * @file math.h * @brief Mathematical utility functions * * This file provides basic mathematical operations * including basic arithmetic and advanced functions. */
#include <cmath>
namespace math {
/** * @brief Calculate the greatest common divisor * @param a First integer * @param b Second integer * @return The GCD of a and b * @pre a and b must be non-negative * * This function uses the Euclidean algorithm to compute * the greatest common divisor efficiently. * * @code * int result = gcd(48, 18); // returns 6 * @endcode */int gcd(int a, int b);
/** * @brief A class representing a 2D point */class Point {public: /** * @brief Constructor * @param x X coordinate * @param y Y coordinate */ Point(double x, double y) : x_(x), y_(y) {}
/** * @brief Get X coordinate * @return X coordinate value */ double x() const { return x_; }
/** * @brief Get Y coordinate * @return Y coordinate value */ double y() const { return y_; }
private: double x_; double y_;};
} // namespace math16.3.3 生成文档
Section titled “16.3.3 生成文档”# 生成 HTML 和 LaTeX 文档doxygen Doxyfile
# 查看 HTML 文档open docs/html/index.html16.4 跨平台开发
Section titled “16.4 跨平台开发”16.4.1 条件编译
Section titled “16.4.1 条件编译”#include <iostream>
// 检测操作系统#ifdef _WIN32 #include <windows.h> const char* PATH_SEP = "\\"; #define SLEEP(ms) Sleep(ms)#elif __linux__ #include <unistd.h> const char* PATH_SEP = "/"; #define SLEEP(ms) usleep((ms) * 1000)#elif __APPLE__ #include <mach/mach.h> const char* PATH_SEP = "/"; #define SLEEP(ms) usleep((ms) * 1000)#endif
// 检测编译器#ifdef __GNUC__ #define COMPILER "GCC" #define GCC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__)#elif _MSC_VER #define COMPILER "MSVC" #define MSVC_VERSION _MSC_VER#endif
// 检测 C++ 标准#ifdef __cplusplus #if __cplusplus >= 202002L #define CXX_STANDARD "C++20" #elif __cplusplus >= 201703L #define CXX_STANDARD "C++17" #elif __cplusplus >= 201402L #define CXX_STANDARD "C++14" #endif#endif16.4.2 CMake 跨平台配置
Section titled “16.4.2 CMake 跨平台配置”cmake_minimum_required(VERSION 3.16)project(MyProject VERSION 1.0.0 LANGUAGES CXX)
# C++ 标准set(CMAKE_CXX_STANDARD 20)set(CMAKE_CXX_STANDARD_REQUIRED ON)set(CMAKE_CXX_EXTENSIONS OFF)
# 平台特定配置if(WIN32) add_definitions(-D_WIN32_WINNT=0x0601) add_compile_options(/W4)elseif(UNIX AND NOT APPLE) add_compile_options(-Wall -Wextra -pedantic)elseif(APPLE) add_compile_options(-Wall -Wextra)endif()
# 查找依赖find_package(Threads REQUIRED)
# 源文件set(SOURCES src/main.cpp src/utils.cpp)
# 创建可执行文件add_executable(${PROJECT_NAME} ${SOURCES})
# 链接库target_link_libraries(${PROJECT_NAME} PRIVATE Threads::Threads)16.4.3 跨平台文件操作
Section titled “16.4.3 跨平台文件操作”#include <filesystem>#include <iostream>
namespace fs = std::filesystem;
void file_operations() { // 创建目录(跨平台) fs::create_directories("data/output");
// 路径拼接 fs::path config_path = fs::current_path() / "config" / "settings.json";
// 检查存在 if (fs::exists(config_path)) { // 读取文件 }
// 遍历目录 for (const auto& entry : fs::directory_iterator("data")) { std::cout << entry.path() << std::endl; }
// 获取用户目录 fs::path home = fs::path(getenv("HOME") ?: ""); #ifdef _WIN32 home = fs::path(getenv("USERPROFILE") ?: ""); #endif}16.4.4 跨平台线程
Section titled “16.4.4 跨平台线程”#include <thread>#include <mutex>#include <iostream>
#ifdef _WIN32 #include <windows.h>#else #include <pthread.h>#endif
// 线程优先级设置void set_thread_priority(std::thread& t, int priority) {#ifdef _WIN32 SetThreadPriority(t.native_handle(), priority);#else // POSIX: 0-99,值越大优先级越高 struct sched_param param; param.sched_priority = priority; pthread_setschedparam(t.native_handle(), SCHED_FIFO, ¶m);#endif}16.5 常用工具链
Section titled “16.5 常用工具链”16.5.1 CMake 最佳实践
Section titled “16.5.1 CMake 最佳实践”# 目录结构# ├── CMakeLists.txt# ├── cmake/# │ └── FindPackage.cmake# ├── include/# │ └── project/# │ └── header.h# ├── src/# │ └── source.cpp# └── tests/# └── test.cpp
# 主 CMakeLists.txtcmake_minimum_required(VERSION 3.16)project(MyProject VERSION 1.0.0 LANGUAGES CXX)
# 设置 C++ 标准set(CMAKE_CXX_STANDARD 20)set(CMAKE_CXX_STANDARD_REQUIRED ON)
# 导出编译_commandsset(CMAKE_EXPORT_COMPILE_COMMANDS ON)
# 包含 cmake 目录list(APPEND CMAKE_MODULE_PATH "${CMAKE_SOURCE_DIR}/cmake")
# 库add_library(my_lib STATIC src/lib.cpp)
target_include_directories(my_lib PUBLIC ${PROJECT_SOURCE_DIR}/include)
# 可执行文件add_executable(my_app src/main.cpp)
target_link_libraries(my_app PRIVATE my_lib)
# 测试enable_testing()add_subdirectory(tests)16.5.2 vcpkg 依赖管理
Section titled “16.5.2 vcpkg 依赖管理”# 安装 vcpkggit clone https://github.com/Microsoft/vcpkg.git./vcpkg/bootstrap-vcpkg.sh
# 安装依赖vcpkg install nlohmann-json fmt spdlog
# CMake 集成# CMakeLists.txtfind_package(nlohmann_json REQUIRED)find_package(fmt REQUIRED)
target_link_libraries(my_app PRIVATE nlohmann_json::nlohmann_json fmt::fmt)16.5.3 clang-tidy 静态分析
Section titled “16.5.3 clang-tidy 静态分析”# 安装brew install clang-format clang-tidy
# 运行clang-tidy src/*.cpp
# 使用配置文件 .clang-tidyChecks: '-*,modernize-*,readability-*'WarningsAsErrors: ''HeaderFilterRegex: '.*\\.(h|hpp)$'FormatStyle: google16.5.4 cmake-format 代码格式化
Section titled “16.5.4 cmake-format 代码格式化”indentation: 4command_case: lowercasekeyword_case: upper16.6 性能优化建议
Section titled “16.6 性能优化建议”16.6.1 编译器优化选项
Section titled “16.6.1 编译器优化选项”# CMake 设置优化级别if(CMAKE_BUILD_TYPE STREQUAL "Release") target_compile_options(my_app PRIVATE -O3 -march=native # 针对本地 CPU 优化 -mtune=native )elseif(CMAKE_BUILD_TYPE STREQUAL "Debug") target_compile_options(my_app PRIVATE -g -O0 -fsanitize=address,undefined # 调试工具 )endif()16.6.2 常见优化模式
Section titled “16.6.2 常见优化模式”// 1. 预分配容量std::vector<int> v;v.reserve(10000); // 避免多次重分配
// 2. 使用移动语义void process(std::vector<int> data) { // 按值传递,调用方应 std::move // 处理数据}
// 3. 避免不必要的虚函数// 虚函数有 vptr 和 vtable 跳转开销
// 4. 使用 constexprconstexpr int fib(int n) { return n <= 1 ? n : fib(n-1) + fib(n-2);}
// 5. 使用 inline 优化小函数inline int square(int x) { return x * x; }
// 6. 避免 dynamic_cast(运行时类型检查开销大)- 遵循统一的代码风格指南(推荐 Google 风格)
- 单元测试是代码质量的基础
- Doxygen 生成专业文档
- CMake 是跨平台构建的事实标准
- 条件编译处理平台差异
- 静态分析工具提升代码质量
- 性能优化需基于实际 profiling 结果
下章预告:ch17 参考资源。