1.4. C++ 最佳实践
本节总结现代 C++ 开发的最佳实践。
1.4.1. 命名与代码风格
1.4.1.1. 命名约定
// 类名:PascalCase
class MyClass {};
// 函数名:snake_case 或 camelCase(保持一致)
void do_something();
void doSomething();
// 变量名:snake_case
int counter_value;
// 常量:k 前缀或全大写
constexpr int kMaxSize = 100;
constexpr int MAX_SIZE = 100;
// 成员变量:后缀下划线
class Widget {
int value_;
};
1.4.1.2. 头文件保护
// 传统方式
#ifndef MY_HEADER_H
#define MY_HEADER_H
// ...
#endif
// 现代方式(非标准但广泛支持)
#pragma once
1.4.2. RAII 原则
1.4.2.1. 资源管理
// 差:手动管理资源
void bad_example() {
int* arr = new int[100];
if (error) return; // 内存泄漏!
delete[] arr;
}
// 好:使用 RAII
void good_example() {
auto arr = std::make_unique<int[]>(100);
if (error) return; // 自动释放
}
// 文件操作
void process_file(const std::string& path) {
std::ifstream file(path); // RAII 管理文件
// 即使抛异常,文件也会正确关闭
}
1.4.2.2. 自定义 RAII 类
// 使用 scope_guard 模式
template<typename F>
class scope_guard {
F func_;
bool active_;
public:
explicit scope_guard(F f) : func_(std::move(f)), active_(true) {}
~scope_guard() { if (active_) func_(); }
void dismiss() { active_ = false; }
};
// 使用
void example() {
auto* resource = acquire_resource();
auto guard = scope_guard([&]{ release_resource(resource); });
// 如果成功,取消清理
if (success) {
guard.dismiss();
}
}
1.4.3. 类设计原则
1.4.3.1. Rule of Zero/Five
// Rule of Zero:优先使用标准库类型,不写特殊成员函数
class Widget {
std::string name_;
std::vector<int> data_;
// 编译器生成的特殊成员函数就够用了
};
// Rule of Five:如果需要自定义一个,就要考虑所有五个
class Resource {
public:
Resource();
~Resource();
Resource(const Resource&);
Resource& operator=(const Resource&);
Resource(Resource&&) noexcept;
Resource& operator=(Resource&&) noexcept;
};
// 或明确删除
class NonCopyable {
public:
NonCopyable() = default;
NonCopyable(const NonCopyable&) = delete;
NonCopyable& operator=(const NonCopyable&) = delete;
};
1.4.3.2. 接口设计
// 使用 const 正确性
class Container {
public:
// const 成员函数
size_t size() const { return size_; }
// 重载提供 const 和非 const 版本
int& operator[](size_t i) { return data_[i]; }
const int& operator[](size_t i) const { return data_[i]; }
};
// 参数传递指南
void process(const std::string& s); // 只读大对象
void process(std::string_view s); // C++17,更高效
void process(int x); // 小对象直接传值
void process(std::unique_ptr<T> p); // 转移所有权
void process(T* p); // 可选参数,可为 null
void process(T& ref); // 必须有效,会被修改
1.4.4. 错误处理
1.4.4.1. 异常 vs 返回值
// 使用异常:构造函数、运算符、程序无法继续的情况
class File {
public:
File(const std::string& path) {
if (!open(path)) {
throw std::runtime_error("Cannot open file");
}
}
};
// 使用返回值:可预期的失败
std::optional<int> find_value(int key) {
if (/* found */) return value;
return std::nullopt;
}
// C++23: std::expected
std::expected<int, Error> parse(std::string_view s) {
if (/* error */) return std::unexpected(Error::PARSE_ERROR);
return value;
}
1.4.4.2. 错误处理策略
// 使用 nodiscard 防止忽略返回值
[[nodiscard]] int calculate(); // 忽略返回值会警告
// 使用 assert 检查程序逻辑
void process(int* ptr) {
assert(ptr != nullptr); // 仅在 Debug 模式生效
// ...
}
// 使用 static_assert 编译期检查
template<typename T>
class Container {
static_assert(std::is_nothrow_move_constructible_v<T>,
"T must be nothrow move constructible");
};
1.4.5. 性能最佳实践
1.4.5.1. 避免不必要的拷贝
// 使用移动语义
std::vector<std::string> get_names() {
std::vector<std::string> names;
// ...
return names; // RVO 或移动,不会拷贝
}
// 使用 emplace 代替 push
std::vector<std::pair<int, std::string>> vec;
vec.push_back(std::make_pair(1, "hello")); // 可能拷贝
vec.emplace_back(1, "hello"); // 原地构造
// 预分配容器空间
std::vector<int> vec;
vec.reserve(1000); // 避免多次重新分配
1.4.5.2. 使用 string_view
// 差:不必要的字符串拷贝
void process(const std::string& s);
process("literal"); // 创建临时 string
// 好:使用 string_view
void process(std::string_view s);
process("literal"); // 无拷贝
// 注意生命周期!
std::string_view dangerous(std::string&& s) {
return s; // 危险!s 销毁后 view 悬空
}
1.4.6. 现代 C++ 习惯用法
1.4.6.1. 使用 auto
// 好的使用场景
auto it = container.begin(); // 迭代器类型太长
auto ptr = std::make_unique<Widget>(); // 避免重复类型
auto result = complex_calculation(); // 复杂表达式
// 避免的场景
auto x = 1; // 不清楚类型
auto y = func(); // 如果返回类型不明显
1.4.6.2. 使用范围 for
// 优先使用范围 for
for (const auto& item : container) {
// 只读访问
}
for (auto& item : container) {
// 需要修改
}
for (auto item : container) {
// 拷贝每个元素(小对象时可以)
}
// 需要索引时
for (size_t i = 0; i < vec.size(); ++i) {
// 使用 i 和 vec[i]
}
1.4.6.3. 使用 constexpr
// 编译期常量
constexpr int MAX_SIZE = 100;
// 编译期函数
constexpr int factorial(int n) {
return n <= 1 ? 1 : n * factorial(n - 1);
}
// 编译期类
class Point {
public:
constexpr Point(int x, int y) : x_(x), y_(y) {}
constexpr int x() const { return x_; }
constexpr int y() const { return y_; }
private:
int x_, y_;
};
constexpr Point p(1, 2);
static_assert(p.x() == 1);
1.4.7. 代码组织
1.4.7.1. 头文件 vs 实现文件
// widget.h
#pragma once
#include <string>
class Widget {
public:
Widget(std::string name);
void process();
private:
std::string name_;
};
// widget.cpp
#include "widget.h"
Widget::Widget(std::string name) : name_(std::move(name)) {}
void Widget::process() {
// 实现
}
1.4.7.2. 使用 PIMPL 减少编译依赖
// widget.h
#pragma once
#include <memory>
class Widget {
public:
Widget();
~Widget();
void process();
private:
struct Impl;
std::unique_ptr<Impl> pimpl_;
};
// widget.cpp
#include "widget.h"
#include <complex_dependency.h>
struct Widget::Impl {
ComplexType data;
// 实现细节
};
Widget::Widget() : pimpl_(std::make_unique<Impl>()) {}
Widget::~Widget() = default; // 需要在 cpp 中定义
小技巧
遵循 C++ Core Guidelines: https://isocpp.github.io/CppCoreGuidelines/