C ++ 11允许inline namespace,其所有成员也自动包含在namespace中。我想不出这有什么用处-有人可以举一个简短,简洁的例子说明需要inline namespace的情况,这是最惯用的解决方案吗?

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

#1 楼

内联名称空间是类似于符号版本控制的库版本控制功能,但仅在C ++ 11级别(即跨平台)实现,而不是特定二进制可执行格式(即平台特定)的功能。 />
这是一种机制,通过该机制,库作者可以使嵌套名称空间的外观和行为好像其所有声明都在周围的名称空间中一样(可以嵌套内联名称空间,因此“嵌套”名称可以渗透所有名称空间)转到第一个非内联名称空间的方式,外观和行为也好像它们的声明也位于它们之间的任何名称空间中。

作为示例,请考虑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的值,我可以实现其中一种实现。 br />
您几乎是正确的。

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

// 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标准没有为自己的标准库规定内联名称空间名称(不过,我很乐意证明这是错误的),因此它只能用于第三方库,不能用于标准本身(除非编译器供应商同意命名方案)。

评论


+1解释为什么使用命名空间V99;在Stroustrup的示例中不起作用。

–史蒂夫·杰索普(Steve Jessop)
2012年6月13日16:00

同样,如果我从头开始一个全新的C ++ 21实现,那么我就不想在std :: cxx_11中实现很多废话了。并非每一个编译器都会始终实现标准库的所有旧版本,尽管目前很容易想到,将现有的实现添加到新版本中后,将现有的实现留在旧版本中几乎没有什么负担。无论如何。我想该标准可以做的有用的事情使其成为可选的,但是如果存在的话,则带有一个标准名称。

–史蒂夫·杰索普(Steve Jessop)
2012年6月13日在16:02



这还不是全部。 ADL也是一个原因(ADL不会使用指令来遵循),并且名称查找也是如此。 (如果要查找B :: name,则在名称空间B中使用名称空间A会使名称空间B中的名称隐藏名称空间A中的名称-内联名称空间不是这样)。

– Johannes Schaub-小人
2012年6月15日11:27



为什么不只将ifdefs用于完整的矢量实现?所有实现都将在一个命名空间中,但是在预处理之后将仅定义其中之一

– sasha.sochka
2013年9月7日20:53



@ sasha.sochka,因为在这种情况下,您不能使用其他实现。它们将被预处理器删除。使用内联名称空间,您可以通过指定完全限定的名称(或使用关键字)来使用所需的任何实现。

–瓦西里(Vasily Biryukov)
13年11月28日在4:29



#2 楼

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的话用在委员会的动机上。

评论


因此,实际上可以从内联V99命名空间调用最后一个f(1)版本吗?

– Eitan T
2012年6月13日下午13:55

@EitanT:是的,因为全局名称空间使用的是名称空间Mine ;,并且Mine名称空间包含内联名称空间Mine :: V99中的所有内容。

–史蒂夫·杰索普(Steve Jessop)
2012年6月13日在13:56



@Walter:在包含V100.h的发行版中,从文件V99.h中删除了内联。当然,您同时也要修改Mine.h以添加额外的包含。 Mine.h是库的一部分,而不是客户端代码的一部分。

–史蒂夫·杰索普(Steve Jessop)
2012年6月13日14:01



@walter:他们没有安装V100.h,而是在安装名为“ Mine”的库。版本99的“ Mine”中有3个头文件-Mine.h,V98.h和V99.h。版本100的“ Mine”中有4个头文件-Mine.h,V98.h,V99.h和V100.h。头文件的排列是与用户无关的实现细节。如果他们发现一些兼容性问题,这意味着他们需要使用部分或全部代码中的Mine :: V98 :: f,则可以将旧代码中对Mine :: V98 :: f的调用与对Mine :: f的调用混合使用在新编写的代码中。

–史蒂夫·杰索普(Steve Jessop)
2012年6月13日14:11



@Walter正如另一个答案所提到的,模板需要在声明它们的名称空间中专门化,而不是在声明它们的名称空间中专门化。虽然看起来很奇怪,但是在此完成的方式允许您在我的,而不必专门研究Mine :: V99或Mine :: V98。

–贾斯汀时间-恢复莫妮卡
16-3-12在23:54

#3 楼

除了所有其他答案。

内联名称空间可用于编码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可以引用具有相同链接器符号的两种不同类型/布局。

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

所以,我们可以这样写:

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


现在,如果您将查看所创建的每个对象的目标文件,其中一个是使用release版本,另一个是使用debug标志。您会发现链接器符号也包括内联名称空间名称。在这种情况下

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



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


注意符号名称中存在reldbg

现在,如果您尝试将调试与发布模式或反之亦然链接起来,则会得到一个链接器错误,与运行时错误相反。

评论


是的,这很有道理。因此,对于库实现者等而言,更多。

–沃尔特
19年6月21日在16:12

#4 楼

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

使用Qt,您可以使用Q_ENUM_NS获得一些额外的,不错的功能,这又要求封闭的名称空间具有一个元对象,该对象用Q_NAMESPACE声明。但是,为了使Q_ENUM_NS工作,同一文件中必须有一个对应的Q_NAMESPACE。而且只能有一个,否则会出现重复的定义错误。有效地,这意味着所有枚举都必须在同一标头中。 uck。

或者...您可以使用内联名称空间。将枚举隐藏在inline namespace中会导致元对象具有不同的名称,而在用户看来不存在其他名称空间(⁽²⁽)。

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



实际上比这更糟糕。 ;它必须在同一大括号中。
除非您尝试不完全限定元数据就访问元对象,但是元对象几乎从未直接使用过。


评论


您可以使用代码框架来进行素描吗? (理想情况下,不明确引用Qt)。听起来似乎很复杂/不清楚。

–沃尔特
19年8月28日在12:22

不...容易。需要单独的名称空间的原因与Qt实现细节有关。 TBH,很难想象Qt之外的情况会有相同的要求。但是,对于这种特定于Qt的场景,它们实在是有用的!有关示例,请参见gist.github.com/mwoehlke-kitware/…或github.com/Kitware/seal-tk/pull/45。

–马修
19年8月28日在16:34

#5 楼

因此,总而言之,using namespace v99inline namespace是不相同的,在C ++ 11中引入专用关键字(内联)之前,前者是版本库的解决方法,该关键字解决了使用using的问题,同时提供了相同的功能版本控制功能。使用曾经引起ADL问题的using namespace(尽管现在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不兼容而在运行时导致神秘的逻辑错误。即使更改在编译时(API级别)不影响程序,添加新成员也会由于类型定义的更改而导致ABI兼容性。

在这种情况下:

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行),并使意图明确。