C++ 作为一门多范式编程语言,拥有丰富的编程惯用法(idioms)。这些惯用法是经验丰富的 C++ 程序员在实践中总结出的高效解决方案。以下是 C++ 中最重要和常用的惯用法:
1. RAII (Resource Acquisition Is Initialization)
资源获取即初始化是 C++ 最核心的惯用法。
class FileHandle {
public:
FileHandle(const char* filename, const char* mode) {
file = fopen(filename, mode);
if (!file) throw std::runtime_error("File open failed");
}
~FileHandle() {
if (file) fclose(file);
}
// 禁用拷贝
FileHandle(const FileHandle&) = delete;
FileHandle& operator=(const FileHandle&) = delete;
// 可以添加移动语义
FileHandle(FileHandle&& other) noexcept : file(other.file) {
other.file = nullptr;
}
// 其他成员函数...
private:
FILE* file;
};
void useFile() {
FileHandle f("test.txt", "r"); // 资源在构造函数中获取
// 使用文件...
// 资源在析构函数中自动释放
}
2. PIMPL (Pointer to Implementation)
指向实现的指针,用于接口与实现分离。
// widget.h
class Widget {
public:
Widget();
~Widget();
void doSomething();
private:
struct Impl;
std::unique_ptr<Impl> pImpl;
};
// widget.cpp
struct Widget::Impl {
int data;
std::string name;
void helperFunction() { /*...*/ }
};
Widget::Widget() : pImpl(std::make_unique<Impl>()) {}
Widget::~Widget() = default;
void Widget::doSomething() { pImpl->helperFunction(); }
3. CRTP (Curiously Recurring Template Pattern)
奇异递归模板模式,用于静态多态。
template <typename Derived>
class Base {
public:
void interface() {
static_cast<Derived*>(this)->implementation();
}
};
class Derived : public Base<Derived> {
public:
void implementation() {
std::cout << "Derived implementation\n";
}
};
template <typename T>
void useBase(Base<T>& base) {
base.interface();
}
4. Type Erasure
类型擦除,用于运行时多态而不需要继承。
class AnyDrawable {
struct Concept {
virtual ~Concept() = default;
virtual void draw() const = 0;
};
template <typename T>
struct Model : Concept {
Model(T value) : value(std::move(value)) {}
void draw() const override { value.draw(); }
T value;
};
std::unique_ptr<Concept> pimpl;
public:
template <typename T>
AnyDrawable(T value) : pimpl(std::make_unique<Model<T>>(std::move(value))) {}
void draw() const { pimpl->draw(); }
};
// 使用
struct Circle { void draw() const { std::cout << "Drawing circle\n"; } };
struct Square { void draw() const { std::cout << "Drawing square\n"; } };
void drawAll(const std::vector<AnyDrawable>& items) {
for (const auto& item : items) {
item.draw();
}
}
5. SFINAE (Substitution Failure Is Not An Error)
替换失败不是错误,用于模板元编程。
template <typename T>
auto print(const T& value) -> decltype(std::cout << value, void()) {
std::cout << value << std::endl;
}
void print(...) {
std::cout << "[object cannot be printed]" << std::endl;
}
// C++17 后可以用 constexpr if 简化
template <typename T>
void print(const T& value) {
if constexpr (std::is_ostreamable_v<T>) {
std::cout << value << std::endl;
} else {
std::cout << "[object cannot be printed]" << std::endl;
}
}
6. Expression Templates
表达式模板,用于优化数值计算。
template <typename E>
class VecExpression {
public:
double operator[](size_t i) const { return static_cast<const E&>(*this)[i]; }
size_t size() const { return static_cast<const E&>(*this).size(); }
};
class Vec : public VecExpression<Vec> {
std::vector<double> data;
public:
double operator[](size_t i) const { return data[i]; }
size_t size() const { return data.size(); }
// ... 其他成员函数
};
template <typename E1, typename E2>
class VecSum : public VecExpression<VecSum<E1, E2>> {
const E1& u;
const E2& v;
public:
VecSum(const E1& u, const E2& v) : u(u), v(v) {}
double operator[](size_t i) const { return u[i] + v[i]; }
size_t size() const { return u.size(); }
};
template <typename E1, typename E2>
VecSum<E1, E2> operator+(const VecExpression<E1>& u, const VecExpression<E2>& v) {
return VecSum<E1, E2>(static_cast<const E1&>(u), static_cast<const E2&>(v));
}
7. NVI (Non-Virtual Interface)
非虚接口,用于模板方法模式。
class Base {
public:
void execute() {
preExecute();
doExecute();
postExecute();
}
virtual ~Base() = default;
private:
virtual void doExecute() = 0;
void preExecute() { /* 通用前置处理 */ }
void postExecute() { /* 通用后置处理 */ }
};
class Derived : public Base {
private:
void doExecute() override {
// 具体实现
}
};
8. Copy-and-Swap
拷贝并交换,用于实现异常安全的赋值操作。
class Resource {
int* data;
size_t size;
void swap(Resource& other) noexcept {
using std::swap;
swap(data, other.data);
swap(size, other.size);
}
public:
Resource(size_t size) : size(size), data(new int[size]) {}
~Resource() { delete[] data; }
Resource(const Resource& other) : size(other.size), data(new int[other.size]) {
std::copy(other.data, other.data + size, data);
}
Resource& operator=(Resource other) {
swap(other);
return *this;
}
Resource(Resource&& other) noexcept : Resource() {
swap(other);
}
};
9. Rule of Zero/Five
零/五法则,现代C++资源管理准则。
// Rule of Zero - 使用智能成员变量,不需要自定义特殊成员函数
class RuleOfZero {
std::unique_ptr<int> ptr;
std::vector<int> data;
std::string name;
// 编译器生成的默认特殊成员函数足够好
};
// Rule of Five - 需要管理资源时,定义全部五个特殊成员函数
class RuleOfFive {
int* data;
size_t size;
public:
explicit RuleOfFive(size_t size = 0) : size(size), data(size ? new int[size] : nullptr) {}
~RuleOfFive() { delete[] data; }
RuleOfFive(const RuleOfFive& other) : size(other.size), data(size ? new int[size] : nullptr) {
std::copy(other.data, other.data + size, data);
}
RuleOfFive(RuleOfFive&& other) noexcept : data(other.data), size(other.size) {
other.data = nullptr;
other.size = 0;
}
RuleOfFive& operator=(RuleOfFive other) {
swap(*this, other);
return *this;
}
friend void swap(RuleOfFive& first, RuleOfFive& second) noexcept {
using std::swap;
swap(first.data, second.data);
swap(first.size, second.size);
}
};
10. Tag Dispatching
标签分发,用于基于类型特性的函数重载。
namespace tags {
struct contiguous {};
struct non_contiguous {};
}
template <typename Iterator>
void algorithm_impl(Iterator first, Iterator last, tags::contiguous) {
std::cout << "Optimized version for contiguous memory\n";
}
template <typename Iterator>
void algorithm_impl(Iterator first, Iterator last, tags::non_contiguous) {
std::cout << "Generic version for non-contiguous memory\n";
}
template <typename Iterator>
void algorithm(Iterator first, Iterator last) {
using iterator_category = typename std::iterator_traits<Iterator>::iterator_category;
if constexpr (std::is_same_v<iterator_category, std::random_access_iterator_tag>) {
algorithm_impl(first, last, tags::contiguous{});
} else {
algorithm_impl(first, last, tags::non_contiguous{});
}
}
11. Policy-Based Design
基于策略的设计,用于高度可配置的类。
template <typename OutputPolicy, typename LanguagePolicy>
class HelloWorld : private OutputPolicy, private LanguagePolicy {
public:
void run() {
OutputPolicy::output(LanguagePolicy::getMessage());
}
};
class ConsoleOutput {
protected:
void output(const std::string& msg) {
std::cout << msg << std::endl;
}
};
class English {
protected:
std::string getMessage() {
return "Hello, World!";
}
};
class French {
protected:
std::string getMessage() {
return "Bonjour, le monde!";
}
};
// 使用
HelloWorld<ConsoleOutput, English> hello;
hello.run(); // 输出: Hello, World!
12. Barton-Nackman Trick
Barton-Nackman技巧,用于在模板类中定义友元运算符。
template <typename T>
class Wrapper {
T value;
public:
Wrapper(const T& v) : value(v) {}
friend bool operator==(const Wrapper& lhs, const Wrapper& rhs) {
return lhs.value == rhs.value;
}
friend std::ostream& operator<<(std::ostream& os, const Wrapper& w) {
return os << w.value;
}
};
13. Virtual Constructor (Clone)
虚拟构造函数,用于多态复制对象。
class Shape {
public:
virtual ~Shape() = default;
virtual std::unique_ptr<Shape> clone() const = 0;
virtual void draw() const = 0;
};
class Circle : public Shape {
double radius;
public:
explicit Circle(double r) : radius(r) {}
std::unique_ptr<Shape> clone() const override {
return std::make_unique<Circle>(*this);
}
void draw() const override {
std::cout << "Circle with radius " << radius << std::endl;
}
};
void cloneAndDraw(const Shape& shape) {
auto cloned = shape.clone();
cloned->draw();
}
14. Shrink-to-fit
收缩至合适大小,用于优化容器内存使用。
std::vector<int> v = {1, 2, 3, 4, 5};
v.erase(v.begin() + 2); // 删除第三个元素
v.shrink_to_fit(); // 释放未使用的内存
// 或者使用交换技巧(C++11前)
std::vector<int>(v).swap(v);
15. Empty Base Optimization (EBO)
空基类优化,用于无状态策略类的空间优化。
struct Empty {}; // 空类
// 没有EBO
struct NoEBO {
Empty e;
int x;
}; // 通常 sizeof(NoEBO) > sizeof(int)
// 使用EBO
template <typename Base>
struct WithEBO : Base {
int x;
}; // sizeof(WithEBO<Empty>) == sizeof(int)
这些惯用法展示了 C++ 的强大功能和灵活性。掌握这些惯用法可以帮助你写出更高效、更可维护的 C++ 代码。根据具体场景选择合适的惯用法是成为高级 C++ 程序员的关键。
804

被折叠的 条评论
为什么被折叠?



