繁体   English   中英

"什么是内联命名空间?"

[英]What are inline namespaces for?

C++11 允许inline namespace<\/code> ,其所有成员也自动位于封闭的namespace<\/code>中。 我想不出任何有用的应用程序——有人可以给出一个简短的例子来说明需要inline namespace<\/code>以及它是最惯用的解决方案的情况吗?

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

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

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

例如,考虑vector的 STL 实现。 如果我们从 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的值, __cplusplus一个或另一个vector实现。 如果您的代码库是用 C++98 之前的版本编写的,并且您发现在升级编译器时 C++98 版本的vector给您带来了麻烦,那么您“所有”要做的就是找到对代码库中的std::vector并用std::pre_cxx_1997::vector替换它们。

来到下一个标准,STL 供应商只是再次重复这个过程,为std::vector引入一个新的命名空间,支持emplace_back (需要 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>的版本控制技巧,因为这暴露了vector所在的真实命名空间的实现细节定义不是直接std

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

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; 在命名空间Mine内部,但我不必完全理解用例,以便在委员会的动机上接受 Bjarne 的话。

除了所有其他答案。

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

考虑这个例子:

假设您编写了一个函数Foo ,它引用对象 say 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可以引用具有相同链接器符号的两种不同类型/布局。

为了防止这种行为,我们将 struct bar包装到一个内联命名空间中,其中根据 Build 类型, bar的链接器符号会有所不同。

所以,我们可以这样写:

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

现在,如果您查看每个对象的目标文件,您将使用 release 构建一个对象文件,并使用调试标志构建另一个对象文件。 您会发现链接器符号也包含内联命名空间名称。 在这种情况下

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

链接器符号名称可能不同。

注意符号名称中reldbg存在。

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

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

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

或者...您可以使用内联命名空间。 在内inline namespace隐藏枚举会导致元对象具有不同的重整名称,而在用户看来,附加命名空间不存在⁽²⁾。

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


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

  2. 除非您尝试在没有完全限定的情况下访问元对象,否则元对象几乎不会被直接使用。

所以总结一下要点, using namespace v99inline namespace是不一样的,前者是一个解决方法,以版本库专用的关键字(内联)在C ++ 11引入了固定用的问题之前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 not abi_v1 found 错误,而不是实际链接然后在运行时由于 ABI 不兼容而导致神秘的逻辑错误。 添加新成员会由于类型定义的变化而导致 ABI 兼容性,即使它在编译时(API 级别)不会影响程序。

在这种情况下:

namespace library {
    namespace abi_v1 {
        class foo {
        } 
    }

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

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

使用using namespace的功能不如使用inline (因为行外定义无法解析),但提供了与上述相同的 4 个优点。 但真正的问题是,当现在有专用关键字来执行此操作时,为什么还要继续使用解决方法。 这是更好的实践,不那么冗长(必须更改 1 行代码而不是 2 行)并使意图清晰。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM