一、C++惯用法(Idioms)
C++有一些区别于其他语言的独特惯用法,这些是经验丰富的C++开发者总结的最佳实践,理解它们是写出地道C++代码的前提。
1.1 Pimpl(Pointer to Implementation)
// Pimpl:将实现细节隐藏到编译防火墙后面
// 优点:减少编译依赖、降低头文件变更影响
// widget.h(公开头文件)
#pragma once
#include
class Widget {
public:
Widget(); // 构造函数
~Widget(); // 必须完整定义(unique_ptr需要)
Widget(Widget&&); // 移动构造
Widget& operator=(Widget&&); // 移动赋值
void draw();
void resize(int w, int h);
private:
// 指向实现类的指针,不在头文件中包含任何依赖
struct Impl;
std::unique_ptr pImpl_; // unique_ptr支持前向声明
};
// widget.cpp(实现文件)
#include "widget.h"
#include "gizmo.h" // 仅在.cpp中可见,头文件不暴露
#include "renderer.h" // 头文件变更不影响widget.h的用户
struct Widget::Impl {
Gizmo gizmo;
Renderer renderer;
int width_ = 0;
int height_ = 0;
};
Widget::Widget() : pImpl_(std::make_unique()) {}
Widget::~Widget() = default; // unique_ptr完整类型已知
Widget::Widget(Widget&&) = default;
Widget& Widget::operator=(Widget&&) = default;
void Widget::draw() { pImpl_->renderer.draw(pImpl_->gizmo); }
void Widget::resize(int w, int h) {
pImpl_->width_ = w;
pImpl_->height_ = h;
}
// 效果:
// - widget.h 不再依赖 gizmo.h / renderer.h
// - 修改实现不影响用户代码重新编译
// - 编译时间可减少50-80%(对大项目显著)
二、CRTP与Mixin模式
2.1 CRTP(Curiously Recurring Template Pattern)
// CRTP:模板继承,编译期多态(无虚函数开销)
template
class Base {
public:
void interface() {
// 在基类中调用派生类的实现
static_cast(this)->implementation();
}
// 策略:基类提供默认实现
void default_behavior() {
// ...
}
};
class Derived : public Base {
public:
void implementation() {
// 派生类实现
}
};
// 应用场景①:静态多态(替代虚函数)
template
void process(T& obj) {
obj.execute(); // 编译期绑定,无虚表查找
}
// 应用场景②:计数器Mixin
template
class Counted : public T {
inline static int instance_count = 0;
public:
Counted() { ++instance_count; }
~Counted() { --instance_count; }
static int count() { return instance_count; }
};
class MyClass : public Counted {};
// MyClass对象数量 → MyClass::count()
// CRTP vs 虚函数:
// CRTP优点:无虚表、inline内联可能、内联优化更好
// CRTP缺点:编译期绑定、不支持运行时多态(无基类指针)
2.2 Tag Dispatch与Type Dispatch
// Tag Dispatch:基于类型标签的编译期分发
// 用于在模板函数中区分不同类型的处理策略
#include
// 为特定类型提供优化实现
template
void append_one(Container& c) {
// 通过类型标签分派到不同实现
append_one_impl(c, std::is_integral{});
}
// true_type分派路径
template
void append_one_impl(Container& c, std::true_type) {
// 整数类型:++操作
c.push_back(typename Container::value_type(1));
}
// false_type分派路径
template
void append_one_impl(Container& c, std::false_type) {
// 非整数类型:用默认值
c.push_back(typename Container::value_type{});
}
三、GoF设计模式在C++中的实现
3.1 单例模式:双重检查锁定
// C++11线程安全单例(双重检查锁定 + memory_order)
class Singleton {
public:
static Singleton* get_instance() {
Singleton* p = instance.load(std::memory_order_acquire);
if (p == nullptr) {
std::lock_guard lock(mtx_);
p = instance.load(std::memory_order_relaxed);
if (p == nullptr) {
p = new Singleton;
instance.store(p, std::memory_order_release);
}
}
return p;
}
private:
static std::atomic instance;
static std::mutex mtx_;
Singleton() = default;
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
// C++17起:更简洁的局部静态变量方案
// (C++11保证局部静态变量线程安全初始化)
class SingletonSimple {
public:
static SingletonSimple& get() {
static SingletonSimple instance; // 线程安全初始化
return instance;
}
private:
SingletonSimple() = default;
};
// 最佳实践:优先使用局部静态变量(更简洁+编译器优化)
3.2 工厂模式:模板工厂
// 模板工厂:编译期注册,运行时创建
#include
四、Scope Guard与RAII扩展
// Scope Guard:确保退出作用域时执行清理
// 用途:数据库事务回滚、文件句柄关闭、解锁互斥锁
class ScopeGuard {
public:
explicit ScopeGuard(std::function fn) : fn_(fn), dismissed_(false) {}
~ScopeGuard() { if (!dismissed_) fn_(); }
void dismiss() { dismissed_ = true; }
private:
std::function fn_;
bool dismissed_;
};
// C++23已经提供std::scope_exit:
#include
void process() {
auto guard = std::scope_exit([]{ unlock(); });
if (error_condition) {
return; // 自动unlock
}
guard.release(); // 取消自动执行
commit(); // 手动提交
}
// 实用场景:
// ① 数据库事务(回滚)
auto conn = db.connect();
auto tx = conn.begin();
auto rollback = std::scope_exit([&] { tx.rollback(); });
do_work();
tx.commit();
rollback.release();
// ② 临时改变locale
auto locale_guard = std::scope_exit([old = std::locale::global(std::locale::classic())] {});