인라인 네임 스페이스 란 무엇입니까?


334

C ++ 11은 inline namespaces를 허용 하며, 모든 멤버도 자동으로 둘러싸고 namespace있습니다. 나는 이것의 유용한 적용을 생각할 수 없다-누군가 inline namespace가 필요한 상황 과 가장 관용적 인 해결책이 있는 상황에 대한 간략하고 간결한 예를 줄 수 있습니까?

(또한 a namespaceinline하나의 파일로 선언되었지만 다른 파일에있을 수있는 모든 선언이 선언되지 않은 경우 어떤 일이 발생하는지 명확 하지 않습니다. 문제가되지 않습니까?)

답변:


339

인라인 네임 스페이스는 심볼 버전 관리 와 유사한 라이브러리 버전 관리 기능 이지만 특정 바이너리 실행 형식 (예 : 플랫폼 별)의 기능이 아닌 C ++ 11 수준 (예 : 크로스 플랫폼)에서 순수하게 구현됩니다.

라이브러리 작성자는 중첩 된 네임 스페이스를 모든 선언이 주변 네임 스페이스에있는 것처럼 보이게하고 작동 할 수있는 메커니즘입니다 (인라인 네임 스페이스는 중첩 될 수 있으므로 "더 중첩 된"이름은 첫 번째가 아닌 이름까지 퍼집니다) -인라인 네임 스페이스를 사용하고 선언이 그 사이의 네임 스페이스에있는 것처럼 보입니다.

예를 들어의 STL 구현을 고려하십시오 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 이전 버전으로 작성되었고 vector컴파일러를 업그레이드 할 때 C ++ 98 버전으로 인해 문제가 발생하는 경우 "모두"는 참조를 찾는 것 std::vector입니다. 코드베이스와로 대체하십시오 std::pre_cxx_1997::vector.

을위한 새로운 공간 도입, 다음 기본으로 제공하고, STL 공급 업체는 절차를 다시 반복 std::vectoremplace_back(++ 11 C 필요)를 지원하고 하나 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 사용자 코드를 고려하십시오 ( std이미 C ++ 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는 자체 표준 라이브러리에 대해 인라인 네임 스페이스 이름을 지정하지 않으므로 (이 경우 잘못된 것으로 입증되고 싶습니다) 타사 라이브러리에만 사용할 수 있습니다. 표준 자체 (컴파일러 벤더가 이름 지정 체계에 동의하지 않는 한).


23
using namespace V99;Stroustrup의 예에서 왜 효과 가 없는지 설명해 +1
Steve Jessop

3
마찬가지로, 처음부터 완전히 새로운 C ++ 21 구현을 시작하면에 많은 오래된 넌센스를 구현하는 데 부담을 느끼고 싶지 않습니다 std::cxx_11. 모든 컴파일러가 항상 모든 표준 라이브러리의 모든 이전 버전을 구현하는 것은 아닙니다. 현재 새로운 구현을 추가 할 때 기존 구현을 이전에 남겨 두어야한다는 부담이 거의 없다고 생각하고 있습니다. 어쨌든 표준이 유용하게 수행 할 수있는 것은 선택 사항이지만 표준 이름이있는 경우 표준으로 사용한다고 가정합니다.
Steve Jessop

46
그것이 전부가 아닙니다. ADL은 이유 (ADL은 지시문을 사용하지 않을 것)와 이름 조회이기도합니다. ( using namespace A이름 공간 B에서 이름 공간 B의 이름을 찾으면 B::name인라인 이름 공간이 아닌 이름 공간 A의 이름이 숨겨집니다 ).
Johannes Schaub-litb

4
ifdef전체 벡터 구현에 s를 사용하지 않습니까? 모든 구현은 하나의 네임 스페이스에 있지만 전처리 후에
하나만

6
이 경우 다른 구현을 사용할 수 없기 때문에 @ sasha.sochka. 전처리기에 의해 제거됩니다. 인라인 네임 스페이스를 사용하면 정규화 된 이름 (또는 using키워드) 을 지정하여 원하는 구현을 사용할 수 있습니다 .
Vasily Biryukov

70

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의 말을 취하기 위해 유스 케이스를 완전히 이해할 필요는 없습니다.


사실 마지막 f(1)버전은 인라인 V99네임 스페이스 에서 호출 됩니까?
Eitan T

1
@EitanT : 예. 전역 네임 스페이스에는가 using namespace Mine;있고 Mine네임 스페이스에는 인라인 네임 스페이스의 모든 것이 포함되어 있기 때문 Mine::V99입니다.
Steve Jessop

2
@Walter : 를 포함하는 릴리스의 inline파일 V99.h에서 제거 합니다 V100.h. Mine.h물론 추가 포함을 추가하기 위해 동시에 수정 합니다. Mine.h클라이언트 코드의 일부가 아닌 라이브러리의 일부입니다.
Steve Jessop

5
@walter : 그들은 설치하지 않고 V100.h"Mine"이라는 라이브러리를 설치하고 있습니다. - "광산"의 버전 99에서 3 개 헤더 파일이 있습니다 Mine.h, V98.h하고 V99.h. - "광산"의 버전 (100)에서 4 개 헤더 파일이 있습니다 Mine.h, V98.h, V99.hV100.h. 헤더 파일의 배열은 사용자와 관련이없는 구현 세부 사항입니다. 호환성 문제가 발견되면 Mine::V98::f일부 또는 모든 코드에서 구체적으로 사용해야 함을 의미 Mine::V98::f하며 이전 코드의 호출 Mine::f과 새로 작성된 코드의 호출을 혼합 할 수 있습니다 .
Steve Jessop

2
@Walter 다른 답변에서 언급했듯이 템플릿은 선언 된 네임 스페이스를 사용하는 네임 스페이스가 아니라 선언 된 네임 스페이스를 전문화해야합니다. Mine대신 전문해야하는 Mine::V99Mine::V98.
저스틴 타임-복원 모니카

8

다른 모든 답변 외에도.

인라인 네임 스페이스를 사용하여 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을 확인하십시오.

이제 디버그를 릴리스 모드로 또는 그 반대로 링크하려고하면 런타임 오류와 반대로 링커 오류가 발생합니다.


1
그렇습니다. 따라서 이것은 라이브러리 구현 자 등을위한 것입니다.
Walter

3

실제로 인라인 네임 스페이스에 대한 또 다른 용도를 발견했습니다.

Qt는 , 당신은 몇 가지 여분을 얻을, 좋은 사용하여 기능을 Q_ENUM_NS다시 바깥 쪽 네임 스페이스를 선언하는 Meta Object를 가지고 있어야한다, Q_NAMESPACE. 그러나 Q_ENUM_NS작동 Q_NAMESPACE 하려면 동일한 파일 ⁽¹⁾에 해당 파일이 있어야합니다. 그리고 하나만 있거나 중복 정의 오류가 발생합니다. 이것은 사실상 모든 열거가 동일한 헤더에 있어야 함을 의미합니다. 왝.

또는 ... 인라인 네임 스페이스를 사용할 수 있습니다. 에 열거를 숨기면inline namespace메타 개체가 다른 맹 글링 된 이름을 가지지 만 추가 네임 스페이스와 같은 사용자를 찾는 것은 존재하지 않습니다.

따라서 어떤 이유로 든 필요한 경우 하나의 네임 스페이스처럼 보이는 여러 개의 하위 네임 스페이스로 항목을 분할하는 데 유용합니다 . 물론 이것은 using namespace inner외부 네임 스페이스 에 쓰는 것과 비슷 하지만 내부 네임 스페이스의 이름을 두 번 쓰는 DRY 위반이 없습니다 .


  1. 실제로 그것보다 더 나쁩니다. 동일한 중괄호 세트에 있어야합니다.

  2. 메타 객체를 정규화하지 않고 메타 객체에 액세스하려고하지 않는 한 메타 객체는 거의 직접 사용되지 않습니다.


코드 스켈레톤으로 스케치 할 수 있습니까? (이상적으로 Qt에 대한 명시 적 참조가 없음). 그것은 모두 관련이 있거나 명확하지 않은 것처럼 들립니다.
Walter

아닙니다 ... 쉽게. 별도의 네임 스페이스가 필요한 이유는 Qt 구현 세부 사항과 관련이 있습니다. TBH, Qt 외부에서 동일한 요구 사항을 갖는 상황을 상상하기는 어렵습니다. 그러나이 Qt 관련 시나리오에는 유용합니다! 예를 들어 gist.github.com/mwoehlke-kitware/… 또는 github.com/Kitware/seal-tk/pull/45 를 참조하십시오 .
Matthew

0

그래서 요점을 요약,하기 using namespace v99inline namespace같은 아니었다 전자는 사용의 문제 해결 C ++ (11)에 도입 된 전용 키워드 (인라인) 이전 버전의 라이브러리에 대한 해결 방법이었다 using같은 버전 관리 기능을 제공하는 동안을. 사용 using namespaceADL에 문제를 야기하는 데 사용 (ADL 지금 따라 나타나지만 using지시를) 및 아웃 오브 라인 등 라이브러리 클래스 / 기능의 전문화 사용자가하는 것은 작품 그 진정한 네임 스페이스의 완료 외부합니다 (이름을한다면 사용자는 몰랐을 것입니다. 즉, 전문화를 해결하려면 B :: 대신 B :: abi_v2 ::를 사용해야합니다.

//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 가지 장점을 제공합니다. 그러나 실제 문제는 이제 전용 키워드가있을 때 해결 방법을 계속 사용하는 이유입니다. 더 나은 연습, 덜 장황한 (2 대신 1 줄의 코드를 변경해야 함) 의도를 명확하게합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.