【问题标题】:Overload resolution with extern "C" linkage使用外部“C”链接的重载分辨率
【发布时间】:2011-02-01 17:44:29
【问题描述】:

在混合 C/C++ 项目中,我们需要从 C 调用 C++ 函数。被调用的函数被重载为三个独立的函数,但是我们可以从C端忽略它,我们只选择一个最合适的并坚持那个。

有两种方法可以做到这一点:(1) 编写一个带有 extern "C" 函数的小型 C++ 包装器,将调用转发给所选的重载函数,或者 (2) 只声明我们想要的一个函数的骇人听闻的方式从 C 调用为 extern "C"。

问题是,第二个变体是否有任何缺点(除了噩梦和恶业)?换句话说,给定三个重载函数,其中一个被声明为 exern "C",我们应该预料到 C++ 端会出现问题,还是按照标准定义好?

【问题讨论】:

  • 用“hackish”这个词来描述解决方案不是代码味道吗?
  • 是的,这绝对是代码异味,我的一位同事问我这个问题,我建议使用解决方案 1,因为我认为这样更清洁。但与此同时,我对如果我们选择解决方案 2 会发生什么感到好奇......当存在不太不确定的解决方案时,我们不得不问的事实已经是代码味道 :)

标签: c++ c overloading


【解决方案1】:

我相信标准中的语言是专门编写的,以允许只允许一个具有“C”链接的函数,以及任意数量的具有“C++”链接的重载相同名称的其他函数(§[dcl.link]/6 ):

至多一个具有特定名称的函数可以具有 C 语言链接。出现在不同名称空间范围内的具有相同函数名称(忽略限定它的名称空间名称)的具有 C 语言链接的函数的两个声明指的是同一个函数。出现在不同命名空间范围内的具有相同名称(忽略限定它的命名空间名称)的具有 C 语言链接的对象的两个声明引用同一个对象。

标准显示以下示例:

complex sqrt(complex); // C + + linkage by default
extern "C" {
    double sqrt(double); // C linkage
}

【讨论】:

  • 谢谢,我仍然认为我会推荐包装器以避免将来的混乱。
  • @harald:我当然会考虑更清洁。
【解决方案2】:

即使标准允许,代码的未来维护者也可能会感到非常困惑,甚至可能会删除外部“C”,从而破坏 C 代码(可能太晚以至于事件无法链接)。

只需编写包装器。

编辑: 从 C++03 7.5/5 开始:

如果两个声明相同 函数或对象指定不同 链接规范(即, 这些的链接规范 声明指定不同的 字符串文字),程序是 如果声明出现,则格式错误 在同一个翻译单元中,并且 一个定义规则(3.2)适用,如果 声明以不同的方式出现 翻译单位...

我将此解释为不适用,因为具有相同名称的 C 和 C++ 函数实际上不是同一个函数,但这种解释可能是错误的。

然后从 C++03 7.5/6:

至多有一个特定的函数 name 可以有 C 语言链接...

这意味着您可以拥有其他具有相同名称的非 C 链接函数。在这种情况下,C++ 重载。

【讨论】:

  • 非常感谢,我仍然觉得它令人困惑,我认为我会推荐反对它。
  • 在 7.5/5 中我同意你的解释:“同一个函数的两个声明”是指同一个函数,而不是同名的不同函数。所以extern "C" void foo(); void foo(); 是UB,但extern "C" void bar(); void bar(int); 不是。
【解决方案3】:

只要您遵循 extern-C 函数的其他规则(例如它们的特殊名称要求),根据标准将其中一个重载指定为 extern-C 就可以了。如果您碰巧使用指向这些函数的函数指针,请注意语言链接是函数类型的一部分,需要指向该函数的函数指针可能会为您决定问题。

否则,我看不出有任何明显的缺点。即使是复制参数和返回值的潜在劣势也可以通过编译器和实现的细节来缓解,允许您内联函数 - 如果确定这是一个问题。

namespace your_project {  // You do use one, right? :)
  void f(int x);
  void f(char x);
  void f(other_overloads x);
}

extern "C"
void f(int x) {
  your_project::f(x);
}

【讨论】:

  • 有趣的解决方案,如果我做对了,您可以通过在命名空间中包含所有重载来避免整个问题。还有一个带有 C 链接的单独的非命名空间函数。不确定我们是否可以在我们的项目中轻松做到这一点,因为 C++ 代码是遗留的并且源自另一个项目。希望我能接受所有的答案,真的......
  • @harald:通常项目中的所有函数,即使它不打算在其他地方用作库,也应该已经在命名空间中。
【解决方案4】:

(此答案适用于 C++14;其他答案目前为 C++03)。

允许使用重载。如果有某个特定名称的 extern "C" 函数定义,则适用以下条件(括号中对 C++14 的引用):

  • extern "C" 函数的声明必须在该函数名 (7.5/5) 的任何重载声明或定义处可见
  • 在任何地方都不能有其他extern "C" 定义同名的函数或变量。 (7.5/6)
  • 不得在全局范围内声明具有相同名称的重载函数。 (7.5/6)
  • 在与extern "C" 函数相同的命名空间内,不得有另一个具有相同名称和参数列表的函数声明。 (7.5/5)

如果在同一个翻译单元中发生任何违反上述规则的行为,编译器必须对其进行诊断;否则它是未定义的行为,不需要诊断。

所以你的头文件可能看起来像:

namespace foo
{
    extern "C" void bar();
    void bar(int);
    void bar(std::string);
}

最后一个要点是不能仅在链接上重载;这是格式错误的:

namespace foo
{
    extern "C" void bar();
    void bar();               // error
}

但是您可以在不同的命名空间中执行此操作:

extern "C" void bar();
namespace foo
{
    void bar();
}

在这种情况下,非限定查找的正常规则会确定某些代码中的调用bar() 是否找到::barfoo::bar不明确

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-09-12
    • 1970-01-01
    • 2011-01-16
    • 1970-01-01
    • 1970-01-01
    • 2016-05-08
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多