inline namespaces是什么用途?

8 浏览
0 Comments

inline namespaces是什么用途?

C++11允许使用inline namespace,其中所有成员也自动属于封闭的namespace。我无法想到任何有用的应用场景-能否请有人简要、简洁地举个例子,说明在何种情况下需要使用inline namespace,并且它是最符合惯用法的解决方案?

(另外,对我来说不太清楚的是,当一个namespace在某些声明中声明为inline,而在其他文件中的声明中没有声明为inline时,会发生什么。这难道不是在寻找麻烦吗?)

0
0 Comments

内联命名空间主要用于在符号中编码ABI(应用二进制接口)信息或函数的版本。它们被用于提供向后ABI兼容性。内联命名空间允许您向名称修饰(ABI)中注入信息,而不会更改API,因为它们仅影响链接器符号名称。

举个例子,假设您编写了一个函数Foo,它接受一个对象bar的引用并且不返回任何内容。

在main.cpp中声明如下:

struct bar;
void Foo(bar& ref);

编译为目标文件后,查看该文件的符号名称:

$ nm main.o
T__ Z1fooRK6bar 

链接器的符号名称可能会有所不同,但它肯定会在某个地方编码函数和参数类型的名称。

现在,可能会有这样的情况,bar的定义如下:

struct bar{
   int x;
#ifndef NDEBUG
   int y;
#endif
};

根据构建类型,bar可能引用两种具有相同链接器符号的不同类型/布局。

为了防止这种情况,我们将bar结构体包装在一个内联命名空间中,这样根据构建类型,bar的链接器符号将会不同。

因此,我们可以这样写:

#ifndef NDEBUG
inline namespace rel { 
#else
inline namespace dbg {
#endif
struct bar{
   int x;
#ifndef NDEBUG
   int y;
#endif
};
}

现在,对于使用发布模式和调试标志分别构建的每个目标文件,查看其对象文件,您会发现链接器符号还包括内联命名空间的名称。在这种情况下:

$ nm rel.o
T__ ZROKfoo9relEbar
$ nm dbg.o
T__ ZROKfoo9dbgEbar

注意符号名称中存在rel和dbg。

现在,如果您尝试将调试模式与发布模式进行链接,或者反之,您将会得到一个链接错误而不是运行时错误。

这对于库的实现者等更有意义。

0
0 Comments

内联命名空间是一种库版本控制的功能,类似于符号版本控制,但是它是在C++11级别上实现的,而不是特定二进制可执行文件格式的功能。它是一种机制,通过它库的作者可以使嵌套命名空间看起来和行为都像是它的声明在周围命名空间中一样(内联命名空间可以嵌套,因此“更嵌套”的名称一直传递到第一个非内联命名空间,并且看起来和行为都像它们的声明在其中的任何命名空间中一样)。

内联命名空间的出现原因是为了解决库版本控制的问题。在C++98中,如果你的代码库是在C++98之前编写的,当你升级编译器时,你可能会发现C++98版本的vector对你造成了麻烦。使用内联命名空间,你只需在代码库中找到对std::vector的引用,将它们替换为std::pre_cxx_1997::vector即可。

此外,内联命名空间还用于解决模板特化和自定义类型的问题。在C++98中,模板特化必须在其声明的命名空间中进行,而使用using namespace无法实现这一点。内联命名空间可以解决这个问题。

内联命名空间的具体用法是通过在命名空间声明前加上关键字inline来创建内联命名空间。这样,库的作者就可以在不破坏现有代码的情况下进行版本控制和扩展。

总之,内联命名空间是一种用于库版本控制和解决模板特化和自定义类型问题的功能。它允许库的作者在不破坏现有代码的情况下对库进行版本控制和扩展。

0
0 Comments

内联命名空间是为了允许向后兼容的版本控制。可以定义多个内部命名空间,并将最新的一个设置为内联命名空间,或者对于不关心版本控制的人来说,可以将其设置为默认命名空间。最新的命名空间可以是未来的或者前沿的版本,尚未成为默认版本。

内联命名空间的一个示例是:

// file V99.h:
inline namespace V99 {
    void f(int);    // 做得比V98版本更好
    void f(double); // 新功能
    // ...
}
// file V98.h:
namespace V98 {
    void f(int);    // 做某事
    // ...
}
// file Mine.h:
namespace Mine {
#include "V99.h"
#include "V98.h"
}
#include "Mine.h"
using namespace Mine;
V98::f(1);  // 旧版本
V99::f(1);  // 新版本
f(1);       // 默认版本

在上述示例中,为什么不将`using namespace V99;`放在`namespace Mine`内部,我无法立即理解其用例,但是我不必完全理解委员会的动机来接受Bjarne的说法。

那么实际上最后的`f(1)`版本是从内联的`V99`命名空间调用的吗?

是的,因为全局命名空间中有`using namespace Mine;`,而`Mine`命名空间包含了来自内联命名空间`Mine::V99`的所有内容。

但是,要使此功能正常工作,我需要在文件`V99.h`中添加`inline`。当文件`V100.h`出现,并且我也将其包含到`Mine`中时,`f(1)`将调用哪个`f(int)`?

你需要在包含`V100.h`的发布版本中从`V99.h`中移除`inline`。当然,同时还需要修改`Mine.h`,添加额外的包含。`Mine.h`是库的一部分,而不是客户端代码的一部分。

当然,但这假设安装`V100.h`的人也必须重新安装`V99.h`的新版本。如果是这样,为什么不只更新所有的`V99.h`(即使其中的某些部分没有改进),而是整个库呢?我仍然没有看到整个问题的意义。

他们没有安装`V100.h`,他们安装的是一个名为"Mine"的库。在"Mine"的第99个版本中有3个头文件 -- `Mine.h`,`V98.h`和`V99.h`。在"Mine"的第100个版本中有4个头文件 -- `Mine.h`,`V98.h`,`V99.h`和`V100.h`。头文件的排列是与用户无关的实现细节。如果他们发现某个兼容性问题,需要在某些或所有代码中使用特定的`Mine::V98::f`,他们可以在旧代码中混合调用`Mine::V98::f`和在新编写的代码中调用`Mine::f`。

嗯。但是如果"他们"也安装了`Mine.h`等,那么为什么不在`Mine.h`中插入`using namespace V100`呢(就像你自己问的)?这仍然是个谜。

正如其他答案所提到的,模板需要在其声明的命名空间中进行特化,而不是在使用它们的命名空间中进行特化。虽然看起来有些奇怪,但这种方式允许在`Mine`中对模板进行特化,而不必在`Mine::V99`或`Mine::V98`中进行特化。

在示例中,`// default version`解析为哪个版本(旧/新)以及如何确定这一点需要进行澄清。

0