cplusplus.rst 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. C++ 支持
  2. ===========
  3. :link_to_translation:`en:[English]`
  4. .. highlight:: cpp
  5. ESP-IDF 主要使用 C 语言编写,并提供 C 语言 API。但 ESP-IDF 也支持使用 C++ 开发应用程序,与 C++ 开发相关的各种主题在本文档中列出。
  6. ESP-IDF 支持以下 C++ 功能:
  7. - :ref:`cplusplus_exceptions`
  8. - :ref:`cplusplus_multithreading`
  9. - :ref:`cplusplus_rtti`
  10. - :doc:`thread-local-storage` (``thread_local`` 关键字)
  11. - 除部分 :ref:`cplusplus_limitations`,所有由 GCC 实现的 C++ 功能均受支持。有关由 GCC 所实现功能的详细信息,请参阅 `GCC 文档 <https://gcc.gnu.org/projects/cxx-status.html>`_。
  12. ``esp-idf-cxx`` 组件
  13. -------------------------
  14. `esp-idf-cxx <https://github.com/espressif/esp-idf-cxx>`_ 组件为一些 ESP-IDF 中的功能提供了更高级别的 C++ API,该组件可以从 `ESP-IDF 组件注册表 <https://components.espressif.com/components/espressif/esp-idf-cxx>`_ 中获取。
  15. C++ 语言标准
  16. ---------------------
  17. 默认情况下,ESP-IDF 使用 C++23 语言标准和 GNU 扩展 (``-std=gnu++23``) 编译 C++ 代码。
  18. 要使用其他语言标准编译特定组件的源代码,请按以下步骤,在组件的 CMakeLists.txt 文件中设置所需的编译器标志:
  19. .. code-block:: cmake
  20. idf_component_register( ... )
  21. target_compile_options(${COMPONENT_LIB} PRIVATE -std=gnu++11)
  22. 如果组件的公共头文件也需要以该语言标准编译,请使用 ``PUBLIC`` 而非 ``PRIVATE``。
  23. .. _cplusplus_multithreading:
  24. 多线程
  25. --------------
  26. 支持 C++ 线程,互斥锁和条件变量。C++ 线程基于 pthread 构建,而 pthread 封装了 FreeRTOS 任务。
  27. 有关在 C++ 中创建线程的示例,请参阅 :example:`cxx/pthread`。
  28. .. note::
  29. `std::jthread <https://en.cppreference.com/w/cpp/thread/jthread>`_ 的析构函数只能从 :ref:`posix_thread_api` 或 `C++ 线程库 API <https://en.cppreference.com/w/cpp/thread>`_ 创建的任务中安全地调用。
  30. .. _cplusplus_exceptions:
  31. 异常处理
  32. ------------------
  33. ESP-IDF 默认禁用对 C++ 异常处理的支持,可以用 :ref:`CONFIG_COMPILER_CXX_EXCEPTIONS` 选项启用该支持。
  34. 如果抛出了异常处理,却没有相应的 ``catch`` 块,程序将由 ``abort`` 函数终止,并打印回溯信息。有关回溯信息的更多信息,请参见 :doc:`fatal-errors`。
  35. C++ 异常处理应 **仅** 应用于异常情况,即意外情况及罕见情况,如发生频率低于 1% 的事件。**请勿** 将 C++ 异常处理用于流程控制,详情请参阅下文的资源使用部分。有关使用 C++ 异常处理的更多详情,请参阅 `ISO C++ FAQ <https://isocpp.org/wiki/faq/exceptions>`_ 和 `CPP 核心指南 <https://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#S-errors>`_。
  36. 有关 C++ 异常处理的示例,请参阅 :example:`cxx/exceptions`。
  37. C++ 异常处理及所需资源
  38. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  39. 启用异常处理后,应用程序的二进制文件通常会增加几个 KB。
  40. 此外,可能需要为异常处理应急内存池保留一部分 RAM。如果无法从堆内存中分配异常处理对象,则会使用该池中的内存。
  41. 使用 :ref:`CONFIG_COMPILER_CXX_EXCEPTIONS_EMG_POOL_SIZE` 变量可以设置异常处理应急内存池的内存量。
  42. 当且仅当 C++ 异常抛出时,会使用额外的栈内存(约 200 字节),从而从栈内存顶部调用函数,启动异常处理。
  43. 使用 C++ 异常处理的代码的运行时间取决于运行时实际发生的情况。
  44. - 如果没有抛出异常,则异常处理的代码运行速度会更快,因为无需检查错误代码。
  45. - 如果抛出异常,异常处理代码的运行时间会比返回错误代码的代码长几个数量级。
  46. 如果抛出异常,解开栈代码的速度要比返回错误代码慢好几个数量级。所增加的运行时长取决于应用程序的要求和错误处理的实现方式(例如,是否需要用户输入或发送消息到云端)。因此,在实时关键的代码路径中,不应使用会抛出异常的代码。
  47. .. _cplusplus_rtti:
  48. 运行时类型信息 (RTTI)
  49. -------------------------------
  50. ESP-IDF 默认禁用对 RTTI 的支持,可以用 :ref:`CONFIG_COMPILER_CXX_RTTI` 选项启用该支持。
  51. 启用此选项,将以启用了 RTTI 支持的方式编译所有的 C++ 文件,并支持使用 ``dynamic_cast`` 转换和 ``typeid`` 运算符。启用此选项通常会增加几十 KB 的二进制文件大小。
  52. 有关在 ESP-IDF 中使用 RTTI 的示例,请参阅 :example:`cxx/rtti`。
  53. 在 C++ 中进行开发
  54. -----------------
  55. 以下部分提供了在 C++ 中开发 ESP-IDF 应用程序的一些技巧。
  56. 组合 C 和 C++ 代码
  57. ^^^^^^^^^^^^^^^^^^^^^^^^
  58. 当应用程序的不同部分使用 C 和 C++ 开发时,理解 `语言链接性 <https://en.cppreference.com/w/cpp/language/language_linkage>`_ 的概念非常重要。
  59. 为了能够从 C 代码中调用 C++ 函数,该 C++ 函数必须使用 C 链接 (``extern "C"``) 进行 **声明** 和 **定义**:
  60. .. code-block:: cpp
  61. // 在 .h 文件中声明:
  62. #ifdef __cplusplus
  63. extern "C" {
  64. #endif
  65. void my_cpp_func(void);
  66. #ifdef __cplusplus
  67. }
  68. #endif
  69. // 在 .cpp 文件中进行定义:
  70. extern "C" void my_cpp_func(void) {
  71. // ...
  72. }
  73. 为了能够从 C++ 中调用 C 函数,该 C 函数必须使用 C 链接 **声明**:
  74. .. code-block:: C
  75. // 在 .h 文件中声明:
  76. #ifdef __cplusplus
  77. extern "C" {
  78. #endif
  79. void my_c_func(void);
  80. #ifdef __cplusplus
  81. }
  82. #endif
  83. // 在 .c 文件中进行定义:
  84. void my_c_func(void) {
  85. // ...
  86. }
  87. 在 C++ 中定义 ``app_main``
  88. ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  89. ESP-IDF 希望应用程序入口点 ``app_main`` 以 C 链接定义。当 ``app_main`` 在 .cpp 源文件中定义时,必须以 ``extern "C"`` 标识:
  90. .. code-block:: cpp
  91. extern "C" void app_main()
  92. {
  93. }
  94. .. _cplusplus_designated_initializers:
  95. 指定初始化器
  96. ^^^^^^^^^^^^^^^^^^^^^^^
  97. 许多 ESP-IDF 组件会以 :ref:`api_reference_config_structures` 作为初始化函数的参数。用 C 编写的 ESP-IDF 示例通常使用 `指定初始化器 <https://en.cppreference.com/w/c/language/struct_initialization>`_,以可读且可维护的方式填充有关结构体。
  98. C 和 C++ 语言对于指定初始化器有不同的规则。例如,C++23(当前在 ESP-IDF 中默认使用)不支持无序指定初始化、嵌套指定初始化、混合使用指定初始化器和常规初始化器,而对数组进行指定初始化。因此,当将 ESP-IDF 的 C 示例移植到 C++ 时,可能需要对结构体初始化器进行一些更改。详细信息请参阅 `C++ aggregate initialization reference <https://en.cppreference.com/w/cpp/language/aggregate_initialization>`_。
  99. ``iostream``
  100. ^^^^^^^^^^^^
  101. ESP-IDF 支持 ``iostream`` 功能,但应注意:
  102. 1. ESP-IDF 在构建过程中通常会删除未使用的代码。然而,在使用 iostreams 的情况下,仅在其中一个源文件包含 ``<iostream>`` 头文件就会使二进制文件增加大约 200 kB。
  103. 2. ESP-IDF 默认使用简单的非阻塞机制来处理标准输入流 (``stdin``)。要获得 ``std::cin`` 的常规行为,应用程序必须初始化 UART 驱动程序,并启用阻塞模式,详情请参阅 :example_file:`common_components/protocol_examples_common/stdin_out.c`。
  104. .. _cplusplus_limitations:
  105. 限制
  106. -----------
  107. - 链接脚本生成器不支持将具有 C++ 链接的函数单独放置在内存的特定位置。
  108. - 当与模板函数一起使用时,会忽略各种节属性(例如 ``IRAM_ATTR``)。
  109. - vtable 位于 flash 中,在禁用 flash 缓存时无法访问。因此,在 :ref:`iram-safe-interrupt-handlers` 中应避免调用虚拟函数。目前尚无法使用链接器脚本生成器调整 vtable 的放置位置。
  110. - 不支持 C++ 文件系统 (``std::filesystem``) 功能。
  111. 注意事项
  112. -------------
  113. 请勿在 C++ 中使用 ``setjmp``/``longjmp``。``longjmp`` 会在不调用任何析构函数的情况下盲目跳出堆栈,容易引起未定义的行为和内存泄漏。请改用 C++ 异常处理,这类程序可以确保正确调用析构函数。如果无法使用 C++ 异常处理,请使用其他替代方案( ``setjmp``/``longjmp`` 除外),如简单的返回码。