378

C++11 允许inline namespaces,其所有成员也自动包含在namespace. 我想不出任何有用的应用——有人可以给出一个简短的例子来说明inline namespace需要 an 并且它是最惯用的解决方案的情况吗?

(另外,我不清楚当 a在一个而不是所有声明namespace中声明时会发生什么inline,这些声明可能存在于不同的文件中。这不是在自找麻烦吗?)

4

6 回答 6

371

内联命名空间是类似于符号版本控制的库版本控制功能,但纯粹在 C++11 级别(即跨平台)实现,而不是特定二进制可执行格式的功能(即特定于平台)。

它是一种机制,库作者可以通过该机制使嵌套命名空间看起来并像其所有声明都在周围的命名空间中一样(内联命名空间可以嵌套,因此“更多嵌套”的名称一直渗透到第一个非-inline 命名空间,并且看起来和行为就好像它们的声明也在两者之间的任何命名空间中一样)。

例如,考虑vector. 如果我们从 C++ 开始就有内联命名空间,那么在 C++98 中,标头<vector>可能看起来像这样:

namespace std {

#if __cplusplus < 1997L // pre-standard C++
    inline
#endif

    namespace pre_cxx_1997 {
        template <class T> __vector_impl; // implementation class
        template <class T> // e.g. w/o allocator argument
        class vector : __vector_impl<T> { // private inheritance
            // ...
        };
    }
#if __cplusplus >= 1997L // C++98/03 or later
                         // (ifdef'ed out b/c it probably uses new language
                         // features that a pre-C++98 compiler would choke on)
#  if __cplusplus == 1997L // C++98/03
    inline
#  endif

    namespace cxx_1997 {

        // std::vector now has an allocator argument
        template <class T, class Alloc=std::allocator<T> >
        class vector : pre_cxx_1997::__vector_impl<T> { // the old impl is still good
            // ...
        };

        // and vector<bool> is special:
        template <class Alloc=std::allocator<bool> >
        class vector<bool> {
            // ...
        };

    };

#endif // C++98/03 or later

} // namespace std

根据 的值__cplusplus,选择一种或另一种vector实现。如果您的代码库是在 C++98 之前的版本中编写的,并且您在升级编译器时发现 C++98 版本vector给您带来了麻烦,那么“您所要做的就是找到对std::vectorin您的代码库并将它们替换为std::pre_cxx_1997::vector.

std::vector下一个标准来了,STL 供应商只是再次重复这个过程,为with emplace_backsupport(需要 C++11)引入一个新的命名空间并内联那个 iff __cplusplus == 201103L

好的,那么为什么我需要一个新的语言功能呢?我已经可以执行以下操作以获得相同的效果,不是吗?

namespace std {

    namespace pre_cxx_1997 {
        // ...
    }
#if __cplusplus < 1997L // pre-standard C++
    using namespace pre_cxx_1997;
#endif

#if __cplusplus >= 1997L // C++98/03 or later
                         // (ifdef'ed out b/c it probably uses new language
                         // features that a pre-C++98 compiler would choke on)

    namespace cxx_1997 {
        // ...
    };
#  if __cplusplus == 1997L // C++98/03
    using namespace cxx_1997;
#  endif

#endif // C++98/03 or later

} // namespace std

根据 的值__cplusplus,我得到一个或另一个实现。

你几乎是正确的。

考虑以下有效的 C++98 用户代码(在 C++98 中已经允许完全特化存在于命名空间中的模板std):

// I don't trust my STL vendor to do this optimisation, so force these 
// specializations myself:
namespace std {
    template <>
    class vector<MyType> : my_special_vector<MyType> {
        // ...
    };
    template <>
    class vector<MyOtherType> : my_special_vector<MyOtherType> {
        // ...
    };
    // ...etc...
} // namespace std

这是完全有效的代码,其中用户为一组类型提供自己的向量实现,她显然知道比在 STL(她的副本)中找到的更有效的实现。

但是:当特化一个模板时,你需要在它被声明的命名空间中这样做。标准说这vector是在命名空间中声明的std,所以这是用户理所当然地期望特化类型的地方。

此代码适用于非版本化命名空间std,或 C++11 内联命名空间功能,但不适用于使用的版本控制技巧,因为这暴露了未直接定义using namespace <nested>的真正命名空间的实现细节。vectorstd

还有其他漏洞可以用来检测嵌套命名空间(请参阅下面的注释),但内联命名空间会将它们全部塞住。这就是它的全部。对未来非常有用,但 AFAIK 标准并没有为它自己的标准库规定内联命名空间名称(不过我很想被证明是错误的),所以它只能用于第三方库,而不是标准本身(除非编译器供应商同意命名方案)。

于 2012-06-13T15:35:00.963 回答
80

http://www.stroustrup.com/C++11FAQ.html#inline-namespace(由 Bjarne Stroustrup 编写和维护的文档,您认为他应该了解大多数 C++11 功能的动机。 )

据此,它允许版本控制以实现向后兼容性。您定义了多个内部命名空间,并创建了最新的一个inline。或者无论如何,不​​关心版本控制的人的默认设置。我想最新的版本可能是尚未默认的未来版本或尖端版本。

给出的例子是:

// file V99.h:
inline namespace V99 {
    void f(int);    // does something better than the V98 version
    void f(double); // new feature
    // ...
}

// file V98.h:
namespace V98 {
    void f(int);    // does something
    // ...
}

// file Mine.h:
namespace Mine {
#include "V99.h"
#include "V98.h"
}

#include "Mine.h"
using namespace Mine;
// ...
V98::f(1);  // old version
V99::f(1);  // new version
f(1);       // default version

我没有立即明白你为什么不把using namespace V99;namespace 放在里面Mine,但我不必完全理解用例就可以在委员会的动机上接受 Bjarne 的话。

于 2012-06-13T13:52:53.337 回答
21

除了所有其他答案。

内联命名空间可用于编码 ABI 信息或符号中函数的版本。正是由于这个原因,它们被用来提供向后 ABI 兼容性。内联命名空间允许您在不改变 API 的情况下将信息注入错位名称 (ABI),因为它们仅影响链接器符号名称。

考虑这个例子:

假设您编写了一个函数,该函数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

现在,如果您尝试将调试与发布模式或反之亦然链接,您将收到与运行时错误相反的链接器错误。

于 2019-06-21T01:11:23.773 回答
5

我实际上发现了内联命名空间的另一种用途。

使用Qt,您可以获得一些额外的、不错的特性 using Q_ENUM_NS,这反过来又要求封闭的命名空间有一个元对象,它用Q_NAMESPACE. 然而,为了工作,在同一个文件中Q_ENUM_NS必须有一个对应的⁽¹⁾。而且只能有一个,否则会出现重复的定义错误。这实际上意味着您的所有枚举都必须在同一个标​​题中。呸。Q_NAMESPACE

或者...您可以使用内联命名空间。在 an 中隐藏枚举inline namespace会导致元对象具有不同的错位名称,而在用户看来,额外的命名空间并不存在⁽²⁾。

因此,如果您出于某种原因需要这样做,它们对于将内容拆分为多个看起来像一个命名空间的子命名空间很有用。当然,这类似于using namespace inner在外部命名空间中写入,但没有两次写入内部命名空间的名称的DRY违规。


  1. 实际上比这更糟。它必须在同一组大括号中。

  2. 除非您尝试在不完全限定元对象的情况下访问元对象,但元对象几乎从未直接使用过。

于 2019-08-27T20:24:45.500 回答
4

因此,总结要点,using namespace v99inline namespace并不相同,前者是在 C++11 中引入专用关键字(inline)之前版本库的一种解决方法,它解决了使用问题using,同时提供相同的版本控制功能。使用using namespace过去会导致 ADL 出现问题(尽管 ADL 现在似乎遵循using指令),并且如果在真正的命名空间(其名称为用户不会也不应该知道,即用户必须使用 B::abi_v2:: 而不仅仅是 B:: 来解决专业化问题)。

//library code
namespace B { //library name the user knows
    namespace A { //ABI version the user doesn't know about
        template<class T> class myclass{int a;};
    }
    using namespace A; //pre inline-namespace versioning trick
} 

// user code
namespace B { //user thinks the library uses this namespace
    template<> class myclass<int> {};
}

这将显示静态分析警告first declaration of class template specialization of 'myclass' outside namespace 'A' is a C++11 extension [-Wc++11-extensions]。但是,如果您将命名空间 A 内联,则编译器会正确解析特化。虽然,使用 C++11 扩展,问题就消失了。

using使用;时无法解析行外定义 它们必须在嵌套/非嵌套扩展命名空间块中声明(这意味着用户需要再次知道 ABI 版本,如果出于某种原因允许他们提供自己的函数实现)。

#include <iostream>
namespace A {
    namespace B{
        int a;
        int func(int a);
        template<class T> class myclass{int a;};
        class C;
        extern int d;
    } 
    using namespace B;
} 
int A::d = 3; //No member named 'd' in namespace A
class A::C {int a;}; //no class named 'C' in namespace 'A' 
template<> class A::myclass<int> {}; // works; specialisation is not an out-of-line definition of a declaration
int A::func(int a){return a;}; //out-of-line definition of 'func' does not match any declaration in namespace 'A'
namespace A { int func(int a){return a;};} //works
int main() {
    A::a =1; // works; not an out-of-line definition
}

使 B 内联时问题就消失了。

命名空间的其他功能inline是允许库编写者提供对库的透明更新 1) 无需强制用户使用新的命名空间名称重构代码 2) 防止缺乏冗长 3) 提供与 API 无关的细节的抽象,同时 4) 提供与使用非内联命名空间相同的有益链接器诊断和行为。假设您正在使用一个库:

namespace library {
    inline namespace abi_v1 {
        class foo {
        } 
    }
}

它允许用户在library::foo不需要知道或在文档中包含 ABI 版本的情况下调用,看起来更干净。使用library::abiverison129389123::foo会看起来很脏。

当对 进行更新时foo,即向类添加新成员时,它不会影响 API 级别的现有程序,因为它们不会已经在使用该成员,并且内联命名空间名称的更改不会改变 API 级别的任何内容因为library::foo仍然可以工作。

namespace library {
    inline namespace abi_v2 {
        class foo {
            //new member
        } 
    }
}

但是,对于与其链接的程序,因为内联名称空间名称像常规名称空间一样被分解为符号名称,因此更改对链接器来说是不透明的。因此,如果应用程序没有重新编译而是与新版本的库链接,它会出现符号abi_v1未找到错误,而不是实际链接然后由于ABI不兼容而导致运行时出现神秘的逻辑错误。添加新成员会由于类型定义的变化而导致 ABI 兼容性,即使它在编译时(API 级别)不会影响程序。

在这种情况下:

namespace library {
    namespace abi_v1 {
        class foo {
        } 
    }

    inline namespace abi_v2 {
        class foo {
            //new member
        } 
    }
}

就像使用 2 个非内联命名空间一样,它允许链接新版本的库,而无需重新编译应用程序,因为abi_v1它将被其中一个全局符号破坏,并且它将使用正确的(旧)类型定义。但是,重新编译应用程序会导致引用解析为library::abi_v2.

Usingusing namespace的功能比 using 少inline(因为行外定义无法解决),但提供了与上述相同的 4 个优点。但真正的问题是,既然现在有专门的关键字来解决问题,为什么还要继续使用解决方法。这是更好的做法,不那么冗长(必须更改 1 行代码而不是 2 行),并使意图明确。

于 2020-03-28T13:50:44.367 回答
1

内联命名空间也可用于提供对命名空间内功能/名称的细粒度访问。

这用于std::literals. literalsstd 中的命名空间都是内联命名空间,因此:

  • 如果您在using namespace std;某处使用,您还可以访问标准中所有用户定义的文字。
  • 但是,如果您只需要本地代码中的一组 udl,您也可以这样做using namespace std::literals::string_literals;,您将获得该命名空间中定义的 udl 符号。

对于您想要访问非限定符号(udl、运算符等)的符号,这似乎是一种有用的技术,您可以将它们捆绑在内联命名空间中,以便您可以仅在该(子)命名空间上执行特定使用整个库的命名空间。

于 2022-02-02T08:01:10.120 回答