C++外部声明隔离

9
请看以下内容:
namespace N {
    extern "C" void f();
}

void g() {
    N::f();
}

这段代码在命名空间内声明了一个具有C语言链接的外部函数。这使得可以从私有命名空间中引用这样的函数,避免普通全局外部声明所造成的命名空间污染。它还允许客户端代码发出其他(希望兼容的)对同一函数的声明而不会冲突,即使是来自供应商提供的头文件包含的全局命名空间。

我经常在C和C++中依赖类似的结构来隔离编译与一些库提供的糟糕或冲突的头文件。 (在C中,通过在函数范围内发出所需的声明来实现此目的,如果不是因为不允许在函数范围内进行extern链接声明,则在C ++中也可能实现。)这对于正确链接到定义良好的ABI特别有用,而无需依赖供应商提供的头文件。

是否可以使用具有常规C ++链接的函数或方法执行相同操作?也就是说:在私有命名空间(或任何类型的本地作用域)中声明具有C ++链接的外部函数,但其可能引用实际在另一个命名空间中定义的函数?

预期功能(伪代码):

namespace N {
    // Actually should link with P::f() (and not N::f()).
    extern "C++" void f();
}

void g() {
    N::f(); // P::f();
}

这显然不是源文件(区别于头文件)的问题,因为在这种情况下命名空间污染并不重要。 因此,这个问题主要涉及隔离库头文件中的声明(用于模板和内联函数)。
欢迎使用特定于编译器的解决方案(MSVC和GCC受到关注)。
例如:假设我的库名为“Lib1”,我想在“Lib1”命名空间中声明所有内容。
// Lib1.hpp
namespace Lib1 {
    class Class1;
    void func1();
    // ...
}

现在假设我的库引用了另一个库Lib2,这是由其他人提供的C库。

/* Lib2.h */
#ifdef __cplusplus
extern "C" {
#endif

struct Struct2;
void func2();
/* ... */

#ifdef __cplusplus
}
#endif

在我的库中,如果需要的话,我可以引用来自Lib2的实体而无需包含Lib2.h
// Lib1.hpp
namespace Lib1 {
    extern "C" void func2();

    inline void inlineX() {
        func2();
    }
}

同时,客户端代码可以自由地包含Lib1.hppLib2.h(如果符合C++友好性),而不会发生冲突。

现在,假设有第三个库Lib3,它是一个C++库,并在Lib3命名空间中声明实体。

// Lib3.hpp
namespace Lib3 {
    class Class3;
    void func3();
    // ...
}

有没有一种方法可以像处理Lib2那样处理Lib3?也就是说,在不包括Lib3.hpp的情况下,在Lib1.hpp中引用Lib3的实体,但仍允许客户端代码无需麻烦地包括Lib1.hppLib3.hpp
如果在Lib1中声明:
// Lib1.hpp
namespace Lib3 {
    void func3();
}

namespace Lib1 {
    inline void inlineY() {
        Lib3::func3();
    }
}

如果客户端代码同时包含Lib1.hppLib3.hpp,就可能发生冲突--当然,在这个简单的示例中声明是相同的,但是在实际情况下微小的差异可以触发语法级别的警告或错误,即使底层ABI相同,因为这违反了不在Lib1命名空间之外声明任何内容的前提条件。希望这能帮助理解问题。

2
你的C语言链接示例似乎无效: 7.5/6...具有C语言链接的实体不应该与全局范围内的实体同名,除非这两个声明表示相同的实体... - Igor Tandetnik
为什么你想要写N来表示P呢?为什么不直接写P呢? - Igor Tandetnik
首先想到的是“最小惊讶原则”。 "extern"C"只涉及名称混淆,不做其他任何事情。因此,你所做的实际上是一种“黑魔法”,不应该在其他人会读到的代码中使用。代码是为人们编写的,因此最好手动编写函数转发器。 - SigTerm
我不确定这是否是你想要的或者想要防止的内容,但在命名空间中使用extern "C"会导致一个带有C名称混淆的符号(没有命名空间!)。因此,保护只会在您的代码内部生效。 - dornhege
@Alek和我在我的第二条评论中已经给了你一个解决方案。 - Kal
显示剩余7条评论
1个回答

1
这不就是C++中using指令的预期用法吗?
// Lib3.hpp
#pragma once
namespace Lib3 {
    void func3();
}

// Lib1.hpp
#include <Lib3.hpp>
namespace Lib1 {
    using Lib3::func3;
}

感谢您的回答。 实际上,想法是根本不必在Lib3 中声明 Lib3::func3() - 无论如何,而是在Lib1某个函数(或类)的局部范围内_某种方式_声明它。 让我提醒您C链接案例:尽管func2()在Lib1内部声明,但它引用了Lib1外部的内容 - 我想知道是否可以对C++链接实体进行相同的操作。 然而,通过观察问题的进展,我感到这个问题在语法层面上没有解决方法,如果有的话只能在链接层面上解决(一些符号重命名之类的)。 - alecov
换句话说,您想要 using 的效果,但是您还想防止 Lib1 的用户直接使用 Lib3?有时可以通过 #define Lib3 __Lib3_internal_to_Lib1 ; #include <Lib3.h> ; #undef Lib3 来实现这种效果。 - Quuxplusone
事实上,我想允许Lib1的用户直接使用Lib3,但是Lib1本身与Lib3的任何头文件都是独立的(而且根本不声明Lib3命名空间中的任何内容)。 在C语言中会发生以下情况:Lib1访问C接口,并允许其他人随意包含Lib2.h(因为Lib1声明的与Lib2相关的任何内容都位于Lib1内部)。 因此,我需要仅在本地可见的方式引用Lib3实体(无论是在本地范围还是在Lib1库命名空间内),而且#define技巧会导致链接错误,不是吗? - alecov
我已经整理了我的示例,以使我所建议的更清晰。您对这个示例有什么不喜欢的地方吗? - Quuxplusone
@Alek,不,当然不行。虽然从语言学家的角度来看,在C语言中你可以这样做,但是即使在C语言中,从软件设计角度来看,你也不能以鲁棒性的方式来实现;如果Lib3的下一个版本添加了func3的参数或更改了其返回类型,则当调用方尝试调用其未正确声明的func3时,会导致未定义行为。唯一非易碎的将Lib3::func3声明引入作用域的方法是#include声明它的头文件。 - Quuxplusone
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接