假设我有一个包含声明为void g(void (*callback)());
以下代码的函数的 C 库是优雅但非法的:
struct A
{
// error C2159: more than one storage class specified (VC++ Nov 2012 CTP)
static extern "C" void callback()
{}
};
g(A::callback);
为什么 C++11 不支持这个?
这是一个特别令人困惑的话题。让我们攻击§7.5“链接规范”[dcl.link]。
1) 所有函数类型、带外部链接的函数名、带外部链接的变量名都有语言链接。
请注意,语言链接的属性适用于两种完全不同的实体:类型和名称。
一个函数在其类型中有一个通常不可见的信息位,用于标识它符合哪个 ABI:C 调用约定、Pascal、Fortran,所有这些都可能被指定以不同的方式使用堆栈,因此通过指针调用它们需要知道看不见的语言标签。
来自另一种语言的变量或函数的名称可以通过 C++ 在语法上访问,或者从引用 C++ 声明的另一种语言中访问。但并不是每一种语言都能与 C++ 的命名方案和 OO 模型相匹配。所以这个方案中的接口不包括类。
因为这些东西是分开管理的,所以它的类型(调用约定)和它的名称(链接器符号)可能有不同的链接。
4) 联动规格嵌套。当链接规范嵌套时,最里面的一个确定语言链接。链接规范不建立范围。链接规范应仅出现在命名空间范围 (3.3) 中。在链接规范中,指定的语言链接适用于所有函数声明符的函数类型、具有外部链接的函数名以及链接规范中声明的具有外部链接的变量名。在确定类成员名称和类成员函数的函数类型的语言链接时,忽略了 AC 语言链接。
影响所有函数声明,extern "C" {}
包括指针和引用,成员函数除外。由于函数只能在命名空间中定义或作为成员定义,因此 C 函数只能在命名空间范围内定义。
该标准在这里给出了一个例子:
extern "C" typedef void FUNC_c();
class C {
// the name of the function mf1 and the member
// function’s type have C++ language linkage; the
// parameter has type pointer to C function
void mf1(FUNC_c*);
// the name of the function mf2 and the member
// function’s type have C++ language linkage
FUNC_c mf2;
// the name of the data member q has C++ language
// linkage and the data member’s type is pointer to
// C function
static FUNC_c* q;
};
不过,您可以使用 模拟您想要的行为typedef
。从第 7.5/4 节中的另一个示例,
extern "C" typedef void FUNC();
// the name f2 has C++ language linkage and the
// function’s type has C language linkage
FUNC f2;
将这些示例与您的示例相结合,您可以拥有
extern "C" typedef void callback_t();
callback_t A_callback; // declare function with C++ name and C type
struct A
{
static callback_t &callback; // not a member function
};
// in source file:
// definition matches semantics of declaration, although not syntax
void A_callback() { ... }
// define static member reference
callback_t &A::callback = A_callback;
g(A::callback); // call syntax is emulated
在实践中,它很少有所作为。只要您不尝试传递或返回非 POD C++ 类类型,C 和 C++ 在大多数平台上都使用兼容的调用约定(有关例外情况,请参阅本页上 Jonathan Wakely 的评论)。这是 C++ 的一个较少实现的特性,因为术语的重载和从微妙到学术的概念区别令人困惑。
首先,函数声明是合法的。但是extern "C"
,类成员会忽略 ,因此如果g
需要
extern "C" void (*)()
,则不能通过callback
。
至于为什么会这样,我怀疑最初主要是正交性的问题:类成员函数extern "C"
一般是没有意义的,而正交性(或者根本没有人考虑静态成员的情况)意味着这也适用于静态成员函数,尽管允许它们extern "C"
是有用的。今天(即 C++11),更改规则会出现问题,因为它可能会破坏现有代码。恕我直言,这种变化是可以接受的,因为它会破坏的代码量可能非常小,并且破坏会导致编译时错误——而不是运行时语义的变化——因此很容易检测和修复。尽管如此,据我所知,没有人提出改变这一点的建议,所以它没有改变。