C 程序中带有默认参数的 C++ 头文件

C++ header file with default arguments in C program

我有一个 C++ 库,其函数在头文件中声明。我的函数声明包含默认参数。

我想通过 Wolfram Mathematica WSTP 模板编译器 (wscc) 在 Mathematica 中使用这个库。这需要为我的库编写一个 C 接口。这个我用过 pattern

#ifdef __cplusplus
extern "C" {
#endif

double my_function(double x, double abs_error = 1E-3);

#ifdef __cplusplus
}
#endif

防止在我的库中进行名称修改(用 C++ 编译)。但是默认参数呢?我不认为它们是标准 C。从 Wolfram Mathematica WSTP 模板编译器 (wscc),我发现

error: expected ‘;’, ‘,’ or ‘)’ before ‘=’ token double abs_error = 1E-3,

我是否必须分别进行 C 和 C++ 声明(本质上是两个头文件)?这是一个常见问题还是与我使用 wscc 有关?也许 wscc 不支持这种语法,虽然它通常是可以接受的?

C does not support default arguments.

因此,我假设您想为您的 C++ 代码保留它们,但您可以要求 C 调用者(在您的例子中是 Mathematica)为所有参数传递值。

一种可能的方法是定义一个宏,它在 C++ 中扩展为默认值初始值设定项,但在 C 中什么也没有。它不漂亮,但它有效:

#ifdef __cplusplus
#define DEFAULT_VALUE(x) = x
#else
#define DEFAULT_VALUE(x)
#endif

#ifdef __cplusplus
extern "C" {
#endif

void foo(int x DEFAULT_VALUE(42), void *y DEFAULT_VALUE(nullptr));

// In C, this becomes   void foo(int x, void *y);
// In C++, this becomes void foo(int x = 42, void *y = nullptr);

#ifdef __cplusplus
}
#endif

extern C 不仅仅可以停止名称修改。该函数具有 C 调用约定。它告诉 CPP 编译器 "you should now speak C"。这意味着异常不会传播,等等。而且 AFAIK C 没有默认值。

您可以在 CPP 文件中实现带有 C 调用约定的函数,这有时非常有用。您可以让 C 调用 CPU 代码,这很有用。

我怀疑编译器如何处理默认值取决于编译器编写者。如果这是真的,我至少可以想到几种方法,其中一种方法不涉及在调用 my_function 时将 abs_error 的值放在堆栈上。例如,编译器可能在堆栈上包含一个参数计数,函数本身使用该参数来发现 abs_error 尚未传递并设置默认值。然而,如果这样的编译器使用具有 C 调用约定但不报告错误的函数来执行此操作,那么它确实会非常不友好。我想我会测试它,只是为了确定。

与其使用宏黑客来解决 C 不支持默认参数的事实,不如引入一个间接层。

首先是您的 C++ 代码使用的特定于 C++ 的 header(我随意命名为 interface.h

 double my_function_caller(double x, double abs_error = 1E-3);

和一个 C 特定的 header(我随意命名 the_c_header.h

 double my_function(double x, double abs_error);

 /*  all other functions that have a C interface here */

实际上,人们可能希望在两个 header 中都包含守卫。

下一步是实际与 mathematica 接口的 C++ 编译单元(我随意命名 interface.cpp

 #include "interface.h"

 extern "C"     //  this is C++, so we don't need to test __cplusplus
 {
      #include "the_c_header.h"
 }

 double my_function_caller(double x, double error)
 {
      return my_function(x, error);
 }

接下来就是如何调用函数的问题了。如果调用者是C++,那么它需要做的就是

  #include "interface.h"

  //   and later in some code

       double result = my_function_caller(x);
       double another_result = my_function_caller(x, 1E-6);

如果调用者是 C 语言(使用 C 编译器构建),它只是

  #include "the_c_header.h"

  /*  and later */

       result = my_function(x, 1E-3);
       another result = my_function(x, 1E-6);

与 macro-based 解决方案相比,这有明显的优点和缺点,包括;

  • None 宏的传统缺点(不考虑作用域,不会与其他宏进行意外交互,运行 违反某些 C++ 开发指南,这些指南禁止将宏用于除包含保护之外的任何用途).
  • 明确区分哪些代码是 C,哪些是 C++:只有 interface.cpp 需要注意同时拥有 #include "the_c_header.h"#include "interface.h",并担心 C++ 的接口机制到 C。否则,C 编译单元(用 C 编译器编译)只需要 #include "the_c_header.h" 而 C++ 编译单元只需要 #include "interface.h".
  • interface.h 可以使用任何 C++ 语言特性(不仅仅是默认参数)。例如,如果您愿意,它声明的所有函数都可以放在名为 mathematica 的命名空间中。使用您的函数的 C++ 开发人员无需关心实际上隐藏在该调用中的 C 代码接口。
  • 如果您将来决定 re-implement my_function() 使用 mathematica 以外的其他东西,您可以。简单地放入 the_c_header.hinterface.cpp 的替换,然后重建。关注点分离意味着无需更改 interface.h,所有 C++ 调用程序甚至不需要在增量构建中重新编译(当然,除非您出于其他原因更改 interface.h) .
  • 实际上,构建过程将检测两个 header 文件的错误使用。 C 编译器会在 interface.h 上阻塞,因为它使用 C++ 特定的功能。 C++ 编译器将在 extern "C" 上下文之外接受 the_c_header.h 的内容,但如果任何 C++ 代码直接调用 my_function()(链接将需要 name-mangled 定义).

简而言之,这比宏方法需要更多的努力来设置,但在长期 运行.

中更容易维护