Không gian tên nội tuyến để làm gì?


334

C ++ 11 cho phép inline namespace, tất cả những thành viên trong số đó cũng sẽ được tự động trong kèm theo namespace. Tôi không thể nghĩ ra bất kỳ ứng dụng hữu ích của việc này - có thể ai đó xin vui lòng cho một ngắn gọn, ví dụ ngắn gọn của một tình huống mà một inline namespacelà cần thiết và nơi mà nó là giải pháp thành ngữ nhất?

(Ngoài ra, nó không phải là rõ ràng với tôi những gì sẽ xảy ra khi một namespacetuyên bố inlinetrong một nhưng không phải tất cả các tờ khai, có thể sống trong các tập tin khác nhau. Đó không phải là cầu xin cho các rắc rối?)

Câu trả lời:


339

Namespace Inline là một thư viện tính năng versioning giống như biểu tượng versioning , nhưng thực hiện hoàn toàn tại C ++ 11 độ (tức. Cross-platform) thay vì là một tính năng của một định dạng cụ thể thực thi nhị phân (nền tảng cụ thể ví dụ.).

Đó là một cơ chế mà theo đó một tác giả thư viện có thể làm cho một cái nhìn namespace lồng nhau và hành động như thể tất cả các tờ khai của nó là trong không gian tên xung quanh (không gian tên nội tuyến có thể được lồng vào nhau, vì vậy "nhiều-lồng" tên thấm lên tất cả các cách để các phi đầu tiên namespace -inline và cái nhìn và hành động như thể tờ khai của họ trong bất kỳ không gian tên ở giữa, quá).

Như một ví dụ, hãy xem xét việc thực hiện STL của vector. Nếu chúng ta có không gian tên inline từ đầu C ++, sau đó trong C ++ 98 tiêu đề <vector>có thể trông như thế này:

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

Tùy thuộc vào giá trị của __cplusplus, hoặc là một hay khác vectorthực hiện được chọn. Nếu codebase của bạn được viết bằng pre-C ++ 98 lần, và bạn thấy rằng C ++ 98 phiên bản của vectorđang gây rắc rối cho bạn khi bạn nâng cấp trình biên dịch của bạn, "tất cả" bạn phải làm là tìm các tài liệu tham khảo để std::vectorở codebase và thay thế chúng bằng bạn std::pre_cxx_1997::vector.

Hãy đến các tiêu chuẩn tiếp theo, và các nhà cung cấp STL chỉ lặp đi lặp lại các thủ tục một lần nữa, giới thiệu một không gian tên mới cho std::vectorvới emplace_backhỗ trợ (mà đòi hỏi C ++ 11) và nội tuyến mà một khi và chỉ khi __cplusplus == 201103L.

OK, vậy tại sao tôi cần một tính năng ngôn ngữ mới cho việc này? Tôi đã có thể làm như sau để có hiệu quả tương tự, phải không?

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

Tùy thuộc vào giá trị của __cplusplus, tôi nhận được một hoặc một trong những triển khai.

Và bạn sẽ gần như đúng.

Hãy xem xét mã người dùng C ++ 98 hợp lệ sau đây (nó đã được phép hoàn toàn chuyên biệt các mẫu sống trong không gian tên stdtrong 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

Đây là mã hoàn toàn hợp lệ khi người dùng cung cấp một vectơ riêng cho một tập hợp loại mà ở đó cô ấy rõ ràng biết cách triển khai hiệu quả hơn so với mã được tìm thấy trong (bản sao của cô ấy) STL.

Nhưng : Khi chuyên biệt một mẫu, bạn cần phải làm như vậy trong không gian tên được khai báo. Tiêu chuẩn nói rằng vectorđược khai báo trong không gian tên std, vì vậy đó là nơi người dùng mong đợi chuyên môn hóa loại.

Mã này hoạt động với không gian tên không có phiên bản stdhoặc với tính năng không gian tên nội tuyến C ++ 11, nhưng không phải với thủ thuật tạo phiên bản được sử dụng using namespace <nested>, vì điều đó phơi bày chi tiết triển khai rằng không gian tên thật vectorđược xác định không stdtrực tiếp.

Có những lỗ hổng khác mà bạn có thể phát hiện không gian tên lồng nhau (xem bình luận bên dưới), nhưng không gian tên nội tuyến cắm tất cả chúng. Và đó là tất cả để có nó. Vô cùng hữu ích cho tương lai, nhưng AFAIK Standard không quy định tên không gian tên nội tuyến cho thư viện tiêu chuẩn của riêng mình (tuy nhiên tôi rất muốn được chứng minh là sai về điều này), vì vậy nó chỉ có thể được sử dụng cho các thư viện bên thứ ba chứ không phải bản thân tiêu chuẩn (trừ khi các nhà cung cấp trình biên dịch đồng ý về sơ đồ đặt tên).


23
+1 để giải thích lý do tại sao using namespace V99;không hoạt động trong ví dụ của Stroustrup.
Steve Jessop

3
Và tương tự, nếu tôi bắt đầu triển khai C ++ 21 hoàn toàn mới từ đầu, thì tôi không muốn bị gánh nặng khi thực hiện nhiều điều vô nghĩa cũ std::cxx_11. Không phải mọi trình biên dịch sẽ luôn luôn triển khai tất cả các phiên bản cũ của các thư viện tiêu chuẩn, mặc dù hiện tại nó rất hấp dẫn khi nghĩ rằng sẽ rất ít gánh nặng khi yêu cầu các triển khai hiện có phải bỏ đi khi cũ, vì thực tế tất cả đều là dù sao đi nữa Tôi cho rằng những gì tiêu chuẩn có thể đã làm một cách hữu ích được làm cho nó trở thành tùy chọn, nhưng với một tên tiêu chuẩn nếu có.
Steve Jessop

46
Đó không phải là tất cả. ADL cũng là một lý do (ADL sẽ không tuân theo chỉ thị) và tra cứu tên cũng vậy. ( using namespace Atrong một không gian tên B làm cho các tên trong không gian tên B ẩn tên trong không gian tên A nếu bạn tìm kiếm B::name- không phải như vậy với các không gian tên nội tuyến).
Julian Schaub - litb

4
Tại sao không sử dụng ifdefs để thực hiện vector đầy đủ? Tất cả các triển khai sẽ ở trong một không gian tên nhưng chỉ một trong số chúng sẽ được xác định sau khi tiền xử lý
sasha.sochka

6
@ sasha.sochka, vì trong trường hợp này bạn không thể sử dụng các triển khai khác. Chúng sẽ được gỡ bỏ bởi tiền xử lý. Với các không gian tên nội tuyến, bạn có thể sử dụng bất kỳ triển khai nào bạn muốn bằng cách chỉ định tên (hoặc usingtừ khóa) đủ điều kiện .
Vasily Biryukov

70

http://www.stroustrup.com/C++11FAQ.html#inline-namespace (một tài liệu được viết và duy trì bởi Bjarne Stroustrup, người mà bạn nghĩ nên nhận thức được hầu hết các động lực cho hầu hết các tính năng của C ++ 11. )

Theo đó, đó là cho phép phiên bản cho khả năng tương thích ngược. Bạn xác định nhiều không gian tên bên trong và tạo một không gian gần đây nhất inline. Hoặc dù sao, một mặc định cho những người không quan tâm đến phiên bản. Tôi cho rằng phiên bản gần đây nhất có thể là phiên bản tương lai hoặc tiên tiến chưa được mặc định.

Ví dụ được đưa ra là:

// 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

Tôi không thấy ngay tại sao bạn không đặt using namespace V99;vào không gian tên Mine, nhưng tôi không phải hoàn toàn hiểu trường hợp sử dụng để lấy từ của Bjarne cho động lực của ủy ban.


Vì vậy, trên thực tế f(1)phiên bản cuối cùng sẽ được gọi từ V99không gian tên nội tuyến ?
Eitan T

1
@EitanT: có, bởi vì không gian tên toàn cầu có using namespace Mine;Minekhông gian tên chứa mọi thứ từ không gian tên nội tuyến Mine::V99.
Steve Jessop

2
@Walter: bạn xóa inlinekhỏi tệp V99.htrong bản phát hành bao gồm V100.h. Tất nhiên, bạn cũng sửa đổi Mine.hđể thêm một phần bổ sung. Mine.hlà một phần của thư viện, không phải là một phần của mã máy khách.
Steve Jessop

5
@walter: họ không cài đặt V100.h, họ đang cài đặt một thư viện gọi là "Của tôi". Có 3 tập tin tiêu đề trong phiên bản 99 của "Mine" - Mine.h, V98.hV99.h. Có 4 tập tin tiêu đề trong phiên bản 100 của "Mine" - Mine.h, V98.h, V99.hV100.h. Việc sắp xếp các tệp tiêu đề là một chi tiết triển khai không liên quan đến người dùng. Nếu họ phát hiện ra một số vấn đề tương thích có nghĩa là họ cần sử dụng cụ thể Mine::V98::ftừ một số hoặc tất cả mã của họ, họ có thể trộn các cuộc gọi đến Mine::V98::ftừ mã cũ với các cuộc gọi đến Mine::fmã mới được viết.
Steve Jessop

2
@Walter Như các câu trả lời khác đề cập, các mẫu cần phải được chuyên môn hóa trong không gian tên mà chúng được khai báo, không phải là một không gian tên sử dụng cái mà chúng được khai báo. Mine, thay vì phải chuyên Mine::V99hoặc Mine::V98.
Thời gian của Justin - Tái lập lại Monica

8

Ngoài tất cả các câu trả lời khác.

Không gian tên nội tuyến có thể được sử dụng để mã hóa thông tin ABI hoặc Phiên bản của các hàm trong các ký hiệu. Đó là do lý do này mà chúng được sử dụng để cung cấp khả năng tương thích ABI lạc hậu. Không gian tên nội tuyến cho phép bạn đưa thông tin vào tên được đọc sai (ABI) mà không thay đổi API vì chúng chỉ ảnh hưởng đến tên biểu tượng của trình liên kết.

Xem xét ví dụ này:

Giả sử bạn viết một hàm Foolấy tham chiếu đến một đối tượng nói barvà không trả về gì.

Nói trong main.cpp

struct bar;
void Foo(bar& ref);

Nếu bạn kiểm tra tên biểu tượng của mình cho tệp này sau khi biên dịch nó thành một đối tượng.

$ nm main.o
T__ Z1fooRK6bar 

Tên biểu tượng liên kết có thể khác nhau nhưng chắc chắn nó sẽ mã hóa tên của các loại hàm và đối số ở đâu đó.

Bây giờ, nó có thể được barđịnh nghĩa là:

struct bar{
   int x;
#ifndef NDEBUG
   int y;
#endif
};

Tùy thuộc vào loại Xây dựng, barcó thể tham khảo hai loại / bố cục khác nhau có cùng ký hiệu liên kết.

Để ngăn chặn hành vi đó, chúng tôi bọc cấu trúc của chúng tôi barvào một không gian tên nội tuyến, trong đó tùy thuộc vào kiểu Build, biểu tượng của trình liên kết barsẽ khác nhau.

Vì vậy, chúng tôi có thể viết:

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

Bây giờ nếu bạn nhìn vào tệp đối tượng của từng đối tượng bạn xây dựng một đối tượng bằng cách sử dụng bản phát hành và khác với cờ gỡ lỗi. Bạn sẽ thấy rằng các biểu tượng liên kết bao gồm cả tên không gian tên nội tuyến. Trong trường hợp này

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

Tên biểu tượng liên kết có thể khác nhau.

Thông báo sự hiện diện của reldbgtrong các tên biểu tượng.

Bây giờ, nếu bạn cố gắng liên kết gỡ lỗi với chế độ phát hành hoặc vise-Versa, bạn sẽ gặp lỗi liên kết trái ngược với lỗi thời gian chạy.


1
Vâng, điều đó có ý nghĩa. Vì vậy, đây là nhiều hơn cho những người thực hiện thư viện và muốn.
Walter

3

Tôi thực sự phát hiện ra một cách sử dụng khác cho không gian tên nội tuyến.

Với Qt , bạn nhận được một số tính năng bổ sung, đẹp mắt bằng cách sử dụng Q_ENUM_NS, do đó yêu cầu không gian tên kèm theo có một siêu đối tượng, được khai báo bằng Q_NAMESPACE. Tuy nhiên, Q_ENUM_NSđể làm việc, phải có một Q_NAMESPACE tệp tương ứng trong cùng một tệp . Và chỉ có thể có một hoặc bạn nhận được các lỗi định nghĩa trùng lặp. Điều này, có hiệu quả, có nghĩa là tất cả các liệt kê của bạn phải ở trong cùng một tiêu đề. Kinh quá.

Hoặc ... bạn có thể sử dụng không gian tên nội tuyến. Ẩn các liệt kê trong mộtinline namespacenguyên nhân khiến các siêu đối tượng có các tên được đọc sai khác nhau, trong khi tìm đến người dùng như không gian tên bổ sung không tồn tại²⁾.

Vì vậy, chúng hữu ích cho việc phân chia nội dung thành nhiều không gian tên phụ trông giống như một không gian tên, nếu bạn cần làm điều đó vì một số lý do. Tất nhiên, điều này tương tự như viết using namespace innertrong không gian tên bên ngoài, nhưng không vi phạm DRY khi viết tên của không gian tên bên trong hai lần.


  1. Nó thực sự tồi tệ hơn thế; nó phải ở trong cùng một bộ niềng răng.

  2. Trừ khi bạn cố gắng truy cập vào siêu đối tượng mà không đủ điều kiện, nhưng đối tượng meta hầu như không được sử dụng trực tiếp.


Bạn có thể phác họa điều đó với một bộ xương mã? (lý tưởng nhất là không tham chiếu rõ ràng đến Qt). Tất cả âm thanh khá liên quan / không rõ ràng.
Walter

Không ... dễ dàng. Lý do không gian tên riêng biệt là cần thiết phải làm với chi tiết thực hiện Qt. TBH, thật khó để tưởng tượng một tình huống bên ngoài Qt sẽ có cùng yêu cầu. Tuy nhiên, đối với kịch bản dành riêng cho Qt này, chúng rất hữu ích! Xem gist.github.com/mwoehlke-kitware/ , hoặc github.com/Kitware/seal-tk/pull/45 để biết ví dụ.
Matthew

0

Vì vậy, để tổng hợp các điểm chính using namespace v99inline namespacekhông giống nhau, trước đây là giải pháp thay thế cho các thư viện phiên bản trước khi một từ khóa chuyên dụng (nội tuyến) được giới thiệu trong C ++ 11 nhằm khắc phục các vấn đề khi sử dụng using, trong khi cung cấp chức năng phiên bản tương tự. Việc using namespacesử dụng được sử dụng để gây ra sự cố với ADL (mặc dù ADL hiện có vẻ tuân theo các usingchỉ thị) và chuyên môn hóa ngoài luồng của một lớp / chức năng thư viện, v.v. bởi người dùng sẽ không hoạt động nếu được thực hiện bên ngoài không gian tên thật (có tên là người dùng sẽ không và không nên biết, tức là người dùng sẽ phải sử dụng B :: abi_v2 :: thay vì chỉ B :: để chuyên môn hóa giải quyết).

//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> {};
}

Điều này sẽ hiển thị một cảnh báo phân tích tĩnh first declaration of class template specialization of 'myclass' outside namespace 'A' is a C++11 extension [-Wc++11-extensions] . Nhưng nếu bạn tạo không gian tên A nội tuyến, thì trình biên dịch sẽ giải quyết chính xác chuyên môn hóa. Mặc dù, với các phần mở rộng C ++ 11, vấn đề không còn nữa.

Các định nghĩa ngoài dòng không giải quyết khi sử dụng using; chúng phải được khai báo trong khối không gian tên mở rộng lồng nhau / không lồng nhau (có nghĩa là người dùng cần biết lại phiên bản ABI, nếu vì bất kỳ lý do gì, chúng được phép cung cấp chức năng thực thi riêng của chúng).

#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
}

Vấn đề biến mất khi thực hiện B nội tuyến.

Các inlinekhông gian tên chức năng khác có cho phép người viết thư viện cung cấp bản cập nhật minh bạch cho thư viện 1) mà không buộc người dùng phải cấu trúc lại mã với tên không gian tên mới và 2) ngăn chặn sự thiếu tính chi tiết và 3) cung cấp sự trừu tượng hóa các chi tiết không liên quan đến API, trong khi 4) đưa ra chẩn đoán và hành vi liên kết có lợi tương tự mà sử dụng một không gian tên phi tuyến sẽ cung cấp. Giả sử bạn đang sử dụng thư viện:

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

Nó cho phép người dùng gọi library::foomà không cần biết hoặc bao gồm phiên bản ABI trong tài liệu, trông có vẻ sạch sẽ hơn. Sử dụng library::abiverison129389123::foosẽ trông bẩn.

Khi một bản cập nhật được thực hiện foo, tức là thêm một thành viên mới vào lớp, nó sẽ không ảnh hưởng đến các chương trình hiện có ở cấp API vì chúng sẽ không sử dụng thành viên VÀ thay đổi trong tên không gian tên nội tuyến sẽ không thay đổi bất cứ điều gì ở cấp API bởi vì library::foovẫn sẽ làm việc

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

Tuy nhiên, đối với các chương trình liên kết với nó, vì tên không gian tên nội tuyến được chuyển thành tên biểu tượng như không gian tên thông thường, thay đổi sẽ không rõ ràng đối với trình liên kết. Do đó, nếu ứng dụng không được biên dịch lại mà được liên kết với một phiên bản mới của thư viện, nó sẽ xuất hiện một biểu tượngabi_v1 không tìm thấy lỗi, thay vì nó thực sự liên kết và sau đó gây ra lỗi logic bí ẩn khi chạy do không tương thích ABI. Thêm một thành viên mới sẽ gây ra khả năng tương thích ABI do thay đổi định nghĩa loại, ngay cả khi nó không ảnh hưởng đến chương trình tại thời gian biên dịch (cấp API).

Trong kịch bản này:

namespace library {
    namespace abi_v1 {
        class foo {
        } 
    }

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

Giống như sử dụng 2 không gian tên phi tuyến, nó cho phép một phiên bản mới của thư viện được liên kết mà không cần biên dịch lại ứng dụng, bởi vì abi_v1sẽ được đọc trong một trong các ký hiệu toàn cục và nó sẽ sử dụng định nghĩa loại chính xác (cũ). Tuy nhiên, việc biên dịch lại ứng dụng sẽ khiến các tham chiếu giải quyết library::abi_v2.

Việc sử dụng using namespaceít chức năng hơn so với sử dụng inline(trong đó định nghĩa ngoài dòng không giải quyết được) nhưng cung cấp 4 lợi thế giống như trên. Nhưng câu hỏi thực sự là, tại sao tiếp tục sử dụng một cách giải quyết khi bây giờ đã có một từ khóa chuyên dụng để làm điều đó. Đó là cách thực hành tốt hơn, ít dài dòng hơn (phải thay đổi 1 dòng mã thay vì 2) và làm cho ý định rõ ràng.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.