一、核心理念与哲学

  1. 倾向于使用 STL 和标准库,而非自己造轮子

    • 认知:STL 是由顶尖专家编写、经过充分测试和高度优化的。自己实现的容器或算法在正确性和性能上很难超越标准库。
    • 惯例:优先使用 std::vector 代替动态数组,使用 std::algorithm(如 std::sort, std::find, std::transform)代替手写循环。
  2. “资源获取即初始化”

    • 认知:这是 C++ 管理资源的基石。资源的生命周期与对象的生命周期绑定。对象构造时获取资源,析构时自动释放。这消除了资源泄漏(内存、文件句柄、锁等)。
    • 惯例:使用智能指针、std::fstreamstd::unique_lock 等 RAII 类来管理资源。
  3. 避免使用裸指针进行所有权管理

    • 认知:裸指针 T* 不表达任何所有权语义。调用者不知道应该由谁、在什么时候调用 delete。这是内存泄漏和悬空指针的根源。
    • 惯例
      • std::unique_ptr<T>: 表示独占所有权。资源在其析构时自动释放。是默认的、首选的智能指针。
      • std::shared_ptr<T>: 表示共享所有权。当最后一个 shared_ptr 析构时释放资源。仅在需要共享所有权的场景下使用,因为有额外的开销。
      • std::weak_ptr<T>: 与 shared_ptr 搭配使用,解决循环引用问题。
  4. 拥抱零开销抽象

    • 认知:你使用的高层抽象(如 STL 容器、算法、范围库)在运行时不应该带来任何额外开销。编译器会将其优化到与手写 C 代码相当甚至更好的性能。
    • 惯例:放心地使用 std::sort,它通常比手写的快速排序更快,因为它针对不同情况进行了高度优化。
  5. 倾向于编译时而非运行时

    • 认知:将工作从运行时转移到编译时,可以提高性能、增强类型安全。
    • 惯例
      • 使用 constexpr 函数和变量进行编译时计算。
      • 使用 static_assert 进行编译时断言。
      • 使用模板元编程(但现代 C++ 更推荐 constexpr 函数,因为它更直观)。

二、具体语法与特性

  1. 智能指针替代 new/delete

    • 惯例
      // 现代 C++
      auto widget = std::make_unique<Widget>(); // C++14
      auto shared_widget = std::make_shared<Widget>(); // C++14
      
      // 避免
      Widget* widget = new Widget;
      // ... 还要记得 delete
      
  2. 使用 auto 关键字

    • 认知auto 让代码更简洁,避免冗长的类型名,并且能保证变量类型一定与初始化表达式一致,防止意外的隐式转换。
    • 惯例
      auto i = 42; // int
      auto name = "hello"; // const char*
      auto list = std::vector<int>{1, 2, 3}; // std::vector<int>
      
      // 在迭代器中特别有用
      for (auto it = vec.begin(); it != vec.end(); ++it) {...}
      // 或者更现代的 range-based for loop
      for (const auto& element : vec) {...}
      
  3. 范围基于的 for 循环

    • 惯例:遍历容器时,优先使用 range-based for loop。
      std::vector<int> vec = {1, 2, 3};
      for (const auto& value : vec) { // 只读时用 const auto&
          std::cout << value << std::endl;
      }
      for (auto& value : vec) { // 需要修改时用 auto&
          value *= 2;
      }
      
  4. nullptr 替代 NULL0

    • 认知nullptr 有明确的类型 std::nullptr_t,可以避免在函数重载时与整型参数混淆。
  5. 强类型枚举

    • 认知:传统的 C 风格枚举会污染外层作用域,并且能隐式转换为整型。
    • 惯例:使用 enum class
      enum class Color { Red, Green, Blue }; // 作用域为 Color::
      Color c = Color::Red;
      // if (c == 0) ... // 错误!不能隐式转换
      
  6. 使用 std::string_view (C++17) 和 std::span (C++20)

    • 认知:它们是“非拥有”的视图,用于传递字符串或数组的只读/可写引用,避免不必要的拷贝。
    • 惯例:在函数参数中,接收字符串但不修改且不取得所有权时,使用 std::string_view;接收数组时,使用 std::span
      void print_string(std::string_view str) {
          std::cout << str << std::endl;
      }
      void process_data(std::span<int> data) {
          for (auto& i : data) { ... }
      }
      
  7. 移动语义与右值引用

    • 认知:理解移动语义可以避免不必要的深拷贝,极大提升性能。对于管理资源的类,应遵循三五法则,考虑实现移动构造函数和移动赋值运算符。
    • 惯例:使用 std::move 来显式将左值转换为右值,以触发移动操作。在函数中返回局部对象时,编译器会自动优化(RVO/NRVO),你不需要也不应该使用 std::move
  8. Lambda 表达式

    • 认知:Lambda 是现代 C++ 的“一等公民”,极大地简化了函数对象的创建,特别是在与 STL 算法配合时。
    • 惯例
      std::vector<int> vec = {5, 3, 1, 4, 2};
      std::sort(vec.begin(), vec.end(), [](int a, int b) { return a > b; }); // 降序排序
      

三、需要避免的“旧”习惯

  1. 避免使用 mallocfree: 使用 new/delete,但最好直接用智能指针和容器。
  2. 避免 C 风格的数组: 使用 std::array(固定大小)或 std::vector(动态大小)。
  3. 避免 C 风格的字符串操作: 使用 std::string
  4. 避免使用裸指针进行资源管理: 如前所述,使用智能指针。
  5. 尽量减少使用宏: 用 constexprinline 函数、enum class、模板等代替。

总结

现代 C++ 的核心思想是:编写安全、清晰、高效且易于维护的代码

  • 安全:通过 RAII、智能指针、范围循环等机制,消除常见的错误来源。
  • 清晰:通过 auto、lambda、算法等提升代码的表达力。
  • 高效:通过移动语义、零开销抽象、编译时计算等保证顶级性能。
  • 易于维护:通过强类型、减少显式资源管理、使用标准组件,让代码更模块化,更不容易出错。

要学习现代 C++,建议从 C++11 的基础特性开始,然后逐步了解 C++141720 乃至 23 的新特性。书籍如 《Effective Modern C++》《C++ Core Guidelines》 是极佳的学习资源。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐