什么是内联命名空间?

新手上路,请多包涵

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

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

原文由 Walter 发布,翻译遵循 CC BY-SA 4.0 许可协议

阅读 681
2 个回答

内联命名空间是类似于 符号版本控制 的库版本控制功能,但纯粹在 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 的值,选择一个或另一个 vector 实现。如果您的代码库是用 C++98 之前的版本编写的,并且您发现 vector 的 C++98 版本在升级编译器时会给您带来麻烦,那么“所有”您必须做是在您的代码库中找到对 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 标准并没有为它自己的标准库规定内联命名空间名称(不过我很想被证明是错误的),所以它只能用于第三方库,而不是标准本身(除非编译器供应商同意命名方案)。

原文由 Marc Mutz - mmutz 发布,翻译遵循 CC BY-SA 3.0 许可协议

所以总结一下要点, 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 being 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 行),并使意图明确。

原文由 Lewis Kelsey 发布,翻译遵循 CC BY-SA 4.0 许可协议

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题