Untuk apa ruang nama sebaris?

334

C ++ 11 memungkinkan inline namespaces, semua anggota yang juga secara otomatis di dalam melampirkan namespace. Saya tidak dapat memikirkan aplikasi yang bermanfaat ini - dapatkah seseorang memberikan contoh singkat dan ringkas tentang situasi di mana suatu inline namespacedibutuhkan dan di mana itu adalah solusi yang paling idiomatis?

(Juga, tidak jelas bagi saya apa yang terjadi ketika a namespacedideklarasikan inlinedalam satu tetapi tidak semua deklarasi, yang dapat hidup dalam file yang berbeda. Bukankah ini meminta masalah?)

Walter
sumber

Jawaban:

339

Ruang nama sebaris adalah fitur versi perpustakaan yang mirip dengan versi simbol , tetapi diterapkan murni pada level C ++ 11 (mis. Lintas-platform) alih-alih menjadi fitur format biner yang dapat dieksekusi spesifik (mis. Platform-spesifik).

Ini adalah mekanisme di mana penulis perpustakaan dapat membuat tampilan namespace bersarang dan bertindak seolah-olah semua deklarasi berada di namespace sekitarnya (inline namespaces dapat bersarang, sehingga "lebih-bersarang" nama-nama merembes sampai ke non pertama -inama namespace dan lihat dan bertindak seolah-olah deklarasi mereka ada di salah satu ruang nama di antara keduanya).

Sebagai contoh, pertimbangkan penerapan STL untuk vector. Jika kita memiliki ruang nama inline dari awal C ++, maka dalam C ++ 98 header <vector>mungkin terlihat seperti ini:

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

Tergantung pada nilai __cplusplus, salah satu atau vectorimplementasi lainnya dipilih. Jika basis kode Anda ditulis dalam pra-C ++ 98 kali, dan Anda menemukan bahwa versi C ++ 98 vectormenyebabkan masalah bagi Anda ketika Anda memutakhirkan kompiler Anda, "yang harus Anda lakukan adalah menemukan referensi std::vectordi basis kode Anda dan ganti dengan std::pre_cxx_1997::vector.

Datang standar berikutnya, dan vendor STL hanya mengulangi prosedur itu lagi, memperkenalkan namespace baru std::vectordengan emplace_backdukungan (yang membutuhkan C ++ 11) dan sebaris satu iff __cplusplus == 201103L.

OK, jadi mengapa saya perlu fitur bahasa baru untuk ini? Saya sudah bisa melakukan yang berikut untuk memiliki efek yang sama, bukan?

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

Bergantung pada nilai __cplusplus, saya mendapatkan salah satu atau yang lain dari implementasi.

Dan Anda akan hampir benar.

Pertimbangkan kode pengguna C ++ 98 yang valid berikut ini (sudah diizinkan untuk sepenuhnya mengkhususkan templat yang tinggal di namespace stddalam 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

Ini adalah kode yang benar-benar valid di mana pengguna memasok implementasi sendiri dari vektor untuk satu set jenis di mana ia tampaknya tahu implementasi yang lebih efisien daripada yang ditemukan dalam (salinannya) STL.

Tetapi : Ketika mengkhususkan suatu templat, Anda perlu melakukannya di namespace yang dideklarasikan. Standar mengatakan itu vectordideklarasikan dalam namespace std, jadi di situlah pengguna berhak mengharapkan untuk mengkhususkan jenis.

Kode ini berfungsi dengan namespace non-versi std, atau dengan fitur namespace C ++ 11 inline, tetapi tidak dengan trik versi yang digunakan using namespace <nested>, karena itu memperlihatkan detail implementasi yang namespace sebenarnya yang vectordidefinisikan tidak stdsecara langsung.

Ada lubang lain di mana Anda dapat mendeteksi namespace bersarang (lihat komentar di bawah), tetapi inpasi namespace pasang semuanya. Dan hanya itu yang ada untuk itu. Sangat berguna untuk masa depan, tetapi AFAIK Standar tidak meresepkan nama namespace inline untuk pustaka standarnya sendiri (meskipun, saya senang terbukti salah tentang hal ini), jadi itu hanya dapat digunakan untuk perpustakaan pihak ketiga, tidak standar itu sendiri (kecuali vendor kompiler menyetujui skema penamaan).

Marc Mutz - mmutz
sumber
23
+1 untuk menjelaskan mengapa using namespace V99;tidak berfungsi dalam contoh Stroustrup.
Steve Jessop
3
Dan juga, jika saya memulai implementasi C ++ 21 yang baru dari awal, maka saya tidak ingin terbebani mengimplementasikan banyak omong kosong lama di std::cxx_11. Tidak setiap kompiler akan selalu mengimplementasikan semua versi lama dari pustaka standar, meskipun saat ini tergoda untuk berpikir bahwa akan sangat sedikit beban untuk meminta implementasi yang ada untuk pergi di yang lama ketika mereka menambahkan yang baru, karena sebenarnya mereka semua bagaimanapun juga. Saya kira apa yang bisa dilakukan oleh standar adalah menjadikannya opsional, tetapi dengan nama standar jika ada.
Steve Jessop
46
Itu tidak semua yang ada untuk itu. ADL juga merupakan alasan (ADL tidak akan mengikuti menggunakan arahan), dan mencari nama juga. ( using namespace Adalam namespace B membuat nama dalam namespace B menyembunyikan nama dalam namespace A jika Anda mencari B::name- tidak demikian dengan inp namespace inline).
Johannes Schaub - litb
4
Mengapa tidak hanya menggunakan ifdefs untuk implementasi vektor penuh? Semua implementasi akan berada dalam satu namespace tetapi hanya satu dari mereka yang akan didefinisikan setelah preprocessing
sasha.sochka
6
@ sasha.sochka, karena dalam hal ini Anda tidak dapat menggunakan implementasi lain. Mereka akan dihapus oleh preprocessor. Dengan ruang nama sebaris Anda dapat menggunakan implementasi apa pun yang Anda inginkan dengan menentukan nama (atau usingkata kunci) yang sepenuhnya memenuhi syarat .
Vasily Biryukov
70

http://www.stroustrup.com/C++11FAQ.html#inline-namespace (dokumen yang ditulis oleh dan dikelola oleh Bjarne Stroustrup, yang Anda pikir harus mengetahui sebagian besar motivasi untuk sebagian besar fitur C ++ 11. )

Menurut itu, ini memungkinkan versi untuk kompatibilitas ke belakang. Anda mendefinisikan beberapa ruang nama bagian dalam, dan membuat yang terbaru inline. Atau, yang default untuk orang-orang yang tidak peduli dengan versi. Saya kira yang paling baru bisa menjadi versi masa depan atau mutakhir yang belum standar.

Contoh yang diberikan adalah:

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

Saya tidak langsung mengerti mengapa Anda tidak memasukkan using namespace V99;namespace Mine, tetapi saya tidak harus sepenuhnya memahami kasus penggunaan untuk mengambil kata-kata Bjarne untuk itu atas motivasi komite.

Steve Jessop
sumber
Jadi sebenarnya f(1)versi terakhir akan dipanggil dari V99namespace inline ?
Eitan T
1
@EitanT: ya, karena namespace global telah using namespace Mine;, dan Minenamespace berisi semuanya dari inline namespace Mine::V99.
Steve Jessop
2
@Walter: Anda menghapus inlinedari file V99.hdalam rilis yang menyertakan V100.h. Anda juga memodifikasi Mine.hpada saat yang sama, tentu saja, untuk menambahkan tambahan. Mine.hadalah bagian dari perpustakaan, bukan bagian dari kode klien.
Steve Jessop
5
@walter: mereka tidak menginstal V100.h, mereka menginstal perpustakaan yang disebut "Milikku". Ada 3 file header dalam versi 99 dari "Milikku" - Mine.h, V98.hdan V99.h. Ada 4 file header dalam versi 100 dari "Mine" - Mine.h, V98.h, V99.hdan V100.h. Pengaturan file header adalah detail implementasi yang tidak relevan bagi pengguna. Jika mereka menemukan beberapa masalah kompatibilitas yang berarti mereka perlu menggunakan secara khusus Mine::V98::fdari beberapa atau semua kode mereka, mereka dapat menggabungkan panggilan Mine::V98::fdari kode lama dengan panggilan ke Mine::fdalam kode yang baru ditulis.
Steve Jessop
2
@Walter Seperti jawaban lain yang disebutkan, template harus dikhususkan dalam namespace tempat mereka dideklarasikan, bukan namespace menggunakan yang mereka nyatakan. Meskipun terlihat aneh, cara ini dilakukan di sana memungkinkan Anda untuk mengkhususkan template di Mine, daripada harus berspesialisasi dalam Mine::V99atau Mine::V98.
Justin Time - Pasang kembali Monica
8

Selain semua jawaban lainnya.

Namespace inline dapat digunakan untuk menyandikan informasi ABI atau Versi fungsi dalam simbol. Karena alasan ini mereka digunakan untuk memberikan kompatibilitas ABI mundur. Ruang nama sebaris memungkinkan Anda menyuntikkan informasi ke dalam nama hancur (ABI) tanpa mengubah API karena hanya memengaruhi nama simbol tautan.

Pertimbangkan contoh ini:

Misalkan Anda menulis fungsi Fooyang mengambil referensi ke objek mengatakan bardan tidak mengembalikan apa pun.

Katakan di main.cpp

struct bar;
void Foo(bar& ref);

Jika Anda memeriksa nama simbol untuk file ini setelah mengompilasinya menjadi objek.

$ nm main.o
T__ Z1fooRK6bar 

Nama simbol tautan mungkin bervariasi tetapi pasti akan menyandikan nama fungsi dan tipe argumen di suatu tempat.

Sekarang, bisa jadi itu bardidefinisikan sebagai:

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

Bergantung pada tipe Bangun, bardapat merujuk ke dua jenis / tata letak yang berbeda dengan simbol tautan yang sama.

Untuk mencegah perilaku seperti itu, kami membungkus struct kami barmenjadi namespace inline, di mana tergantung pada tipe Build simbol linker barakan berbeda.

Jadi, kita bisa menulis:

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

Sekarang jika Anda melihat file objek dari setiap objek yang Anda buat satu rilis menggunakan dan lainnya dengan flag debug. Anda akan menemukan bahwa simbol tautan juga menyertakan nama namespace sebaris. Pada kasus ini

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

Nama simbol Linker mungkin berbeda.

Perhatikan keberadaan reldan dbgdalam nama simbol.

Sekarang, jika Anda mencoba menautkan debug dengan mode rilis atau sebaliknya, Anda akan mendapatkan kesalahan tautan sebagai kebalikan dari kesalahan runtime.

coder3101
sumber
1
Ya, itu masuk akal. Jadi ini lebih untuk pelaksana perpustakaan dan sejenisnya.
Walter
3

Saya benar-benar menemukan kegunaan lain untuk ruang nama sebaris.

Dengan Qt , Anda mendapatkan beberapa tambahan, fitur bagus menggunakan Q_ENUM_NS, yang pada gilirannya mengharuskan namespace yang melampirkan memiliki objek-meta, yang dideklarasikan dengan Q_NAMESPACE. Namun, agar Q_ENUM_NSdapat berfungsi, harus ada yang sesuai Q_NAMESPACE dalam file yang sama ⁽¹⁾. Dan hanya ada satu, atau Anda mendapatkan kesalahan definisi duplikat. Ini, secara efektif, berarti bahwa semua enumerasi Anda harus berada di header yang sama. Yuck.

Atau ... Anda dapat menggunakan ruang nama sebaris. Menyembunyikan enumerasi dalaminline namespacemenyebabkan meta-objek memiliki nama yang berbeda, sementara mencari pengguna seperti namespace tambahan tidak ada ⁽²⁾.

Jadi, mereka berguna untuk memecah hal-hal menjadi beberapa sub-namespace yang semuanya terlihat seperti satu namespace, jika Anda perlu melakukan itu untuk beberapa alasan. Tentu saja, ini mirip dengan menulis using namespace innerdi namespace luar, tetapi tanpa pelanggaran KERING menulis nama namespace bagian dalam dua kali.


  1. Sebenarnya lebih buruk dari itu; itu harus di set kawat gigi yang sama.

  2. Kecuali Anda mencoba mengakses objek-meta tanpa sepenuhnya memenuhi syarat, tetapi objek-meta hampir tidak pernah digunakan secara langsung.

Matius
sumber
Bisakah Anda membuat sketsa dengan kerangka kode? (idealnya tanpa referensi eksplisit ke Qt). Semuanya terdengar agak terlibat / tidak jelas.
Walter
Tidak ... dengan mudah. Alasan ruang nama terpisah diperlukan berkaitan dengan detail implementasi Qt. TBH, sulit membayangkan situasi di luar Qt yang akan memiliki persyaratan yang sama. Namun, untuk skenario khusus Qt ini, semuanya sangat berguna! Lihat gist.github.com/mwoehlke-kitware/… atau github.com/Kitware/seal-tk/pull/45 untuk contoh.
Matius
0

Jadi untuk meringkas poin utama, using namespace v99dan inline namespacetidak sama, yang pertama adalah solusi untuk pustaka versi sebelum kata kunci khusus (inline) diperkenalkan di C ++ 11 yang memperbaiki masalah penggunaan using, sambil menyediakan fungsionalitas versi yang sama. Menggunakan yang using namespacedigunakan untuk menyebabkan masalah dengan ADL (meskipun ADL sekarang tampaknya mengikuti usingarahan), dan spesialisasi out-of-line kelas perpustakaan / fungsi dll oleh pengguna tidak akan bekerja jika dilakukan di luar namespace yang benar (yang namanya pengguna tidak akan dan tidak seharusnya tahu, yaitu pengguna harus menggunakan B :: abi_v2 :: daripada hanya B :: untuk spesialisasi untuk menyelesaikan).

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

Ini akan menampilkan peringatan analisis statis first declaration of class template specialization of 'myclass' outside namespace 'A' is a C++11 extension [-Wc++11-extensions]. Tetapi jika Anda membuat namespace A inline, maka kompiler dengan benar menyelesaikan spesialisasi. Meskipun, dengan ekstensi C ++ 11, masalah ini hilang.

Definisi out-of-line tidak menyelesaikan ketika menggunakan using; mereka harus dideklarasikan dalam blok namespace ekstensi bersarang / tidak bersarang (yang berarti pengguna perlu mengetahui versi ABI lagi, jika karena alasan apa pun mereka diizinkan untuk menyediakan implementasi fungsi mereka sendiri).

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

Masalahnya hilang saat membuat B sebaris.

inlineNamespaces fungsi lain yang dimiliki adalah memungkinkan penulis perpustakaan untuk menyediakan pembaruan transparan ke perpustakaan 1) tanpa memaksa pengguna untuk refactor kode dengan nama namespace baru dan 2) mencegah kurangnya verbositas dan 3) memberikan abstraksi dari rincian yang tidak relevan dengan API, sementara 4) memberikan diagnosa dan perilaku linker bermanfaat yang sama yang menggunakan namespace non-inline. Katakanlah Anda menggunakan perpustakaan:

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

Ini memungkinkan pengguna untuk menelepon library::footanpa perlu tahu atau memasukkan versi ABI dalam dokumentasi, yang terlihat lebih bersih. Menggunakan library::abiverison129389123::fooakan terlihat kotor.

Ketika pembaruan dibuat foo, yaitu menambahkan anggota baru ke kelas, itu tidak akan mempengaruhi program yang ada di tingkat API karena mereka tidak akan menggunakan anggota DAN perubahan dalam nama namespace sebaris tidak akan mengubah apa pun di tingkat API karena library::fooakan tetap bekerja.

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

Namun, untuk program yang menautkannya, karena nama namespace inline diubah menjadi nama simbol seperti namespace biasa, perubahan tidak akan transparan kepada tautan. Oleh karena itu, jika aplikasi tidak dikompilasi tetapi dihubungkan dengan versi baru perpustakaan, itu akan menyajikan simbol abi_v1tidak ditemukan kesalahan, daripada benar-benar menghubungkan dan kemudian menyebabkan kesalahan logika misterius saat runtime karena ketidakcocokan ABI. Menambahkan anggota baru akan menyebabkan kompatibilitas ABI karena perubahan dalam definisi tipe, bahkan jika itu tidak mempengaruhi program pada waktu kompilasi (level API).

Dalam skenario ini:

namespace library {
    namespace abi_v1 {
        class foo {
        } 
    }

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

Seperti menggunakan 2 ruang nama non-inline, ini memungkinkan untuk versi baru perpustakaan untuk ditautkan tanpa perlu mengkompilasi ulang aplikasi, karena abi_v1akan hancur dalam salah satu simbol global dan akan menggunakan definisi tipe yang lama (lama). Namun mengkompilasi ulang aplikasi akan menyebabkan referensi untuk diselesaikan library::abi_v2.

Menggunakan using namespacekurang fungsional daripada menggunakan inline(dalam definisi out of line tidak menyelesaikan) tetapi memberikan 4 keuntungan yang sama seperti di atas. Tetapi pertanyaan sebenarnya adalah, mengapa terus menggunakan solusi ketika sekarang ada kata kunci khusus untuk melakukannya. Ini praktik yang lebih baik, lebih sedikit verbose (harus mengubah 1 baris kode alih-alih 2) dan memperjelas maksudnya.

Lewis Kelsey
sumber