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++ 程序员的关键。