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

C++ header file with default arguments in C program

本文关键字:参数 文件 默认 程序 C++      更新时间:2023-10-16

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

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

#ifdef __cplusplus
extern "C" {
#endif
double my_function(double x, double abs_error = 1E-3);
#ifdef __cplusplus
}
#endif

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

错误:在"="标记之前应使用";"、"、"或")" 双abs_error = 1E-3,

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

C 不支持默认参数。

因此,我假设您想为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

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

。首先是您的C++代码使用的C++特定标头(我任意将其命名为interface.h.

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

和一个特定于 C 的标头(我任意将其命名为the_c_header.h)

double my_function(double x, double abs_error);
/*  all other functions that have a C interface here */

在实践中,人们可能希望在两个标头中包含守卫。

下一步是一个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);

与基于宏观的解决方案相比,这显然有优点和缺点,包括:

没有宏
  • 的传统缺点(不尊重范围,没有与其他宏的意外交互,与一些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 代码接口。
  • 如果你决定将来使用mathematica以外的其他东西重新实现my_function(),你可以。 只需放入the_c_header.hinterface.cpp的替代品,然后重建。 关注点的分离意味着没有必要更改interface.h,所有C++调用者甚至不需要在增量构建中重新编译(当然,除非您出于其他原因更改interface.h)。
  • 实际上,构建过程将检测两个头文件的错误用法。 C 编译器会因使用特定于C++的功能而阻塞interface.h。 C++编译器将接受extern "C"上下文之外的the_c_header.h内容,但如果任何C++代码直接调用my_function(),结果将是链接器错误(链接将需要名称损坏的定义)。

简而言之,这比宏观方法需要更多的努力来设置,但从长远来看更容易维护。

extern C 所做的不仅仅是停止名称重整。该函数具有 C 调用约定。它告诉CPP编译器"你现在应该说C"。这意味着异常不会传播,等等。AFAIK C 没有默认值。

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

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