Satır içi ad alanları ne için?


334

C ++ 11 inline namespace, tüm üyeleri otomatik olarak ekte bulunan s'ye izin verir namespace. Bunun herhangi bir yararlı uygulamasını düşünemiyorum - biri lütfen a'nın inline namespacegerekli olduğu ve en deyimsel çözümün olduğu bir duruma kısa ve özlü bir örnek verebilir mi?

(Ayrıca, a'nın bir dosyada namespacebildirilmesi durumunda ne olduğu açık inlinedeğil, farklı dosyalarda yaşayabilecek tüm bildirimler değil. Bu sorun için yalvarmıyor mu?)

Yanıtlar:


339

Satır içi ad alanları, sembol sürümüne benzer bir kitaplık sürüm oluşturma özelliğidir , ancak belirli bir ikili yürütülebilir biçimin (yani platforma özgü) bir özelliği olmak yerine yalnızca C ++ 11 düzeyinde (yani çapraz platform) uygulanır.

Bir kütüphane yazarının iç içe geçmiş bir ad alanını görüntüleyebileceği ve tüm bildirimleri çevreleyen ad alanındaymış gibi davranabileceği bir mekanizmadır (satır içi ad alanları iç içe yerleştirilebilir, bu nedenle "daha iç içe" adlar, ilk olmayan -içine ad alanı ve bildirimleri aralarındaki ad alanlarının herhangi birindeymiş gibi görün ve davran).

Örnek olarak, STL'nin uygulanmasını düşünün vector. C ++ başından itibaren satır içi ad alanlarımız olsaydı, o zaman C ++ 98'de başlık <vector>şöyle görünebilirdi:

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

Değerine bağlı olarak, __cplusplusbir veya diğer vectoruygulama seçilir. Senin kod temeli 98 kez ++ öncesi C ile yazılmış ve C ++ 98 sürümü bulmak ise vectorsize derleyici yükseltirken sizin için sorun neden oluyor, "tüm" yapmanız gereken başvurular bulmaktır std::vectoriçinde kod tabanınızı değiştirin ve değiştirin std::pre_cxx_1997::vector.

İçin yeni bir ad tanıtan, sonraki standardını Gel ve STL satıcı sadece tekrar prosedürü tekrarlar std::vectorile emplace_back(++ 11 C gerektirir) desteği ve bu bir IFF inlining __cplusplus == 201103L.

Tamam, neden bunun için yeni bir dil özelliğine ihtiyacım var? Aynı etkiye sahip olmak için aşağıdakileri zaten yapabilirim, değil mi?

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

Değerine bağlı olarak __cplusplus, uygulamalardan birini ya da diğerini alıyorum.

Ve neredeyse haklısın.

Aşağıdaki geçerli C ++ 98 kullanıcı kodunu göz önünde bulundurun ( stdC ++ 98'de ad alanında yaşayan şablonları tamamen özelleştirmesine izin verildi ):

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

Bu, kullanıcının STL'de (kopyasını) bulduğudan daha verimli bir uygulama bildiği bir tür kümesi için bir vektör için kendi uygulamasını gerçekleştirdiği mükemmel bir koddur.

Ancak : Bir şablonu uzmanlaştırırken, bildirildiği ad alanında bunu yapmanız gerekir. Standart vector, ad alanında bildirildiğini söyler std, böylece kullanıcı türü doğru bir şekilde özelleştirmeyi bekler.

Bu kod std, sürümlendirilmemiş bir ad alanı veya C ++ 11 satır içi ad alanı özelliğiyle çalışır, ancak kullanılan sürüm hile ile çalışmaz using namespace <nested>, çünkü bu vector, tanımlanmış olan gerçek ad alanının stddoğrudan olmadığı uygulama ayrıntısını gösterir .

İç içe geçmiş ad alanını algılayabileceğiniz başka delikler de vardır (aşağıdaki yorumlara bakın), ancak satır içi ad alanları hepsini tıkar. Ve hepsi bu kadar. Gelecek için son derece yararlı, ancak AFAIK Standardı kendi standart kütüphanesi için satır içi ad alanı isimleri yazmıyor (bununla ilgili yanlış kanıtlanmayı isterim), bu yüzden sadece üçüncü taraf kütüphaneleri için kullanılabilir, değil standardın kendisi (derleyici satıcıları bir adlandırma şeması üzerinde anlaşmadıkça).


23
using namespace V99;Stroustrup örneğinde neden çalışmadığını açıklayan +1 .
Steve Jessop

3
Ve benzer şekilde, yepyeni bir C ++ 21 uygulamasını sıfırdan başlatırsam, o zaman birçok eski saçmalık uygulamaktan dolayı yükümlü olmak istemiyorum std::cxx_11. Şu anda her derleyici, standart kütüphanelerin tüm eski sürümlerini her zaman uygulamamaktadır, ancak şu anda mevcut uygulamaların yeni eklediklerinde eski uygulamalarda bırakılmasını gerektirecek çok az yük olacağını düşünmek cazip gelse de, aslında hepsi her neyse. Sanırım standart olarak ne yapabilirdi isteğe bağlı, ancak varsa standart bir adla.
Steve Jessop

46
Hepsi bu kadar değil. ADL de bir nedendi (ADL direktifleri kullanarak takip etmeyecektir) ve isim araması da. ( using namespace Abir ad alanında B, B ad alanındaki adları yaparsa, A ad alanındaki adları gizler B::name- satır içi ad alanlarında böyle olmaz).
Johannes Schaub - litb

4
Neden sadece ifdeftam vektör uygulaması için s kullanmıyorsunuz ? Tüm uygulamalar tek bir ad alanında olacaktır, ancak ön işlemden sonra sadece bir tanesi tanımlanacaktır
sasha.sochka

6
@ sasha.sochka, çünkü bu durumda başka uygulamaları kullanamazsınız. Önişlemci tarafından kaldırılacaktır. Satır içi ad alanlarıyla, tam olarak nitelenmiş ad (veya usinganahtar kelime) belirterek istediğiniz herhangi bir uygulamayı kullanabilirsiniz .
Vasily Biryukov

70

http://www.stroustrup.com/C++11FAQ.html#inline-namespace (çoğu C ++ 11 özelliği için çoğu motivasyonun farkında olması gerektiğini düşündüğünüz Bjarne Stroustrup tarafından yazılan ve sürdürülen bir belge. )

Buna göre, geriye dönük uyumluluk için sürümlemeye izin vermektir. Birden çok iç ad alanı tanımlarsınız ve en son olanı yaparsınız inline. Her neyse, sürüm oluşturmayı umursamayanlar için varsayılan seçenek. En yenisinin henüz varsayılan olmayan gelecekteki veya en son sürüm olabileceğini düşünüyorum.

Verilen örnek şöyledir:

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

Neden using namespace V99;ad Minealanının içine girmediğinizi hemen anlayamıyorum , ancak Komitenin motivasyonunda Bjarne'nin sözünü almak için kullanım durumunu tam olarak anlamak zorunda değilim.


Yani aslında son f(1)sürüm satır içi V99ad alanından çağrılır mı?
Eitan T

1
@EitanT: evet, çünkü global ad alanı vardır using namespace Mine;ve Minead alanı satır içi ad alanından her şeyi içerir Mine::V99.
Steve Jessop

2
@Walter: içeren sürümdeki inlinedosyadan kaldırırsınız . Ayrıca , ekstra bir ekleme eklemek için aynı zamanda değiştirirsiniz . kitaplığın bir parçasıdır, istemci kodunun bir parçası değildir. V99.hV100.hMine.hMine.h
Steve Jessop

5
@walter: Yüklemiyorlar V100.h, "Benim" adlı bir kütüphane kuruyorlar . - "Mine" sürümü 99 3 başlık dosya yok Mine.h, V98.hve V99.h. - "Mine" sürümü 100 4 başlık dosyaları vardır Mine.h, V98.h, V99.hve V100.h. Başlık dosyalarının düzenlenmesi, kullanıcılarla ilgisi olmayan bir uygulama ayrıntısıdır. Bazı uyumluluk sorunlarıyla karşılaşırlarsa, bu, özellikle Mine::V98::fkodlarının bir kısmından veya tamamından kullanmaları gerektiği anlamına gelir Mine::V98::f, eski koddan gelen çağrıları Mine::fyeni yazılmış koddaki çağrılarla karıştırabilirler .
Steve Jessop

2
@Walter Diğer yanıtlardan bahsedildiği gibi, şablonların beyan edildiklerini kullanan bir ad alanı değil, beyan ettikleri ad alanında uzman olmaları gerekir. Tuhaf görünse de, bunun yapıldığı yol, Mine, uzmanlaşmak yerine Mine::V99veya Mine::V98.
Justin Time - Monica

8

Diğer tüm cevaplara ek olarak.

Satır içi ad alanı, ABI bilgilerini veya sembollerdeki işlevlerin Sürümünü kodlamak için kullanılabilir. Bu nedenle geriye doğru ABI uyumluluğu sağlamak için kullanılırlar. Satır içi ad alanları, yalnızca bağlayıcı sembol adını etkilediği için API'yı değiştirmeden karıştırılmış ada (ABI) bilgi enjekte etmenizi sağlar.

Bu örneği düşünün:

Diyelim ki Foobir nesneye referans alan barve hiçbir şey döndürmeyen bir işlev yazıyorsunuz .

Main.cpp içinde söyle

struct bar;
void Foo(bar& ref);

Bu dosya için sembol adınızı bir nesneye derledikten sonra kontrol ederseniz.

$ nm main.o
T__ Z1fooRK6bar 

Bağlayıcı sembolü adı değişebilir, ancak mutlaka bir yerde işlev ve bağımsız değişken türlerinin adını kodlar.

Şimdi, şu barşekilde tanımlanmış olabilir :

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

Derleme türüne bağlı olarak, baraynı bağlayıcı sembollerine sahip iki farklı türe / düzene başvurabilirsiniz.

Bu tür bir davranışı önlemek için, yapımızı barsatır içi bir ad alanına sararız; burada, Build tipine bağlı olarak linker sembolü barfarklı olur.

Böylece şunu yazabiliriz:

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

Şimdi her bir nesnenin nesne dosyasına bakarsanız, birini release ve diğerini debug bayrağını kullanarak oluşturursunuz. Bağlayıcı sembollerinin satır içi ad alanı adını da içerdiğini göreceksiniz. Bu durumda

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

Bağlayıcı Sembol adları farklı olabilir.

Bildirimi varlığı relve dbgsembol isimlerinde.

Şimdi, hata ayıklama sürümünü yayın modu veya tam tersi ile bağlamaya çalışırsanız, çalışma zamanı hatasının aksine bir bağlayıcı hatası alırsınız.


1
Evet, bu mantıklı. Bu kütüphane uygulayıcıları ve benzerleri için daha fazla.
Walter

3

Aslında satır içi ad alanları için başka bir kullanım keşfettim.

İle Qt , bazı ekstra olsun, güzel kullanan özellikler Q_ENUM_NSsırayla kapsayan ad ile bildirilmiş bir meta nesne, sahip olduğu gerektiren, Q_NAMESPACE. Ancak, Q_ENUM_NSçalışabilmek için, Q_NAMESPACE aynı dosyada corresponding karşılık gelmesi gerekir. Ve sadece bir tane olabilir veya yinelenen tanım hataları alırsınız. Bu, etkili bir şekilde, tüm numaralandırmalarınızın aynı başlıkta olması gerektiği anlamına gelir. Yuck.

Veya ... satır içi ad alanlarını kullanabilirsiniz. inline namespaceEk ad alanı gibi kullanıcılara bakarken, meta nesnelerin farklı karıştırılmış adlara sahip olmasına neden olurken,numaralandırmaların gizlenmeside mümkün değildir.

Yani, onlar birden çok alt ad bölmeyi şeyler için yararlı olduğunu tüm bakış bir ad gibi bir nedenden dolayı bunu yapmak gerekirse. Tabii ki, bu using namespace innerdış ad alanındaki yazmaya benzer , ancak KURU iç ad alanının adını iki kez yazma ihlali olmadan .


  1. Aslında bundan daha kötü; aynı küme parantezinde olmalıdır.

  2. Meta nesneye tam olarak nitelendirmeden erişmeye çalışmadığınız sürece, ancak meta nesne doğrudan hiç kullanılmaz.


Bunu bir kod iskeleti ile çizebilir misin? (ideal olarak Qt'ye açık referans olmadan). Her şey oldukça ilgili / belirsiz görünüyor.
Walter

Kolayca değil. Ayrı ad alanlarına ihtiyaç duyulmasının nedeni Qt uygulama ayrıntılarıyla ilgilidir. TBH, Qt dışında aynı şartlara sahip bir durum hayal etmek zor. Bununla birlikte, bu Qt'a özel senaryo için yararlıdırlar! Örnek için gist.github.com/mwoehlke-kitware/… veya github.com/Kitware/seal-tk/pull/45 adresine bakın .
Matthew

0

Yani ana noktaları özetlemek için using namespace v99ve inline namespaceaynı değildi, eski kullanarak sorunlarını sabit C ++ 11'de tanıtıldı özel bir anahtar kelime (inline) önce versiyon kütüphaneleri için bir geçici çözüm oldu usingaynı sürüm işlevselliği sunarken,. Kullanma using namespaceADL ile neden problemlerine kullanılan (ADL şimdi izleyin görünse de usingdirektifler) ve dışı hattı vb kütüphane sınıf / fonksiyon uzmanlık kullanıcı tarafından değil iş olan gerçek ad alanının bitti dışında (isim olsaydı kullanıcı bilmeyecek ve bilmeyecektir, yani kullanıcı, uzmanlığın çözülmesi için B :: yerine B :: abi_v2 :: kullanmalıdır).

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

Bu statik bir analiz uyarısı gösterecektir first declaration of class template specialization of 'myclass' outside namespace 'A' is a C++11 extension [-Wc++11-extensions]. Ancak, ad alanı A satır içi yaparsanız, derleyici uzmanlığı doğru bir şekilde çözer. Bununla birlikte, C ++ 11 uzantılarında sorun ortadan kalkar.

Hat dışı tanımlar kullanılırken çözülmez using; iç içe / iç içe olmayan bir uzantı ad alanı bloğunda bildirilmeleri gerekir (bu, herhangi bir nedenle, bir işlevi kendi uygulamalarını sağlamalarına izin verildiyse, kullanıcının ABI sürümünü tekrar bilmesi gerektiği anlamına gelir).

#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 sıralı yapılırken sorun ortadan kalkar.

Diğer işlev inlinead alanları, kütüphane yazarının 1) kullanıcıyı yeni ad alanı adıyla kodu yeniden düzenlemeye zorlamadan ve 2) ayrıntı eksikliğini önleme ve 3) API ile ilgili olmayan ayrıntıların soyutlanmasını sağlamadan kütüphaneye şeffaf bir güncelleme sağlamasına izin verir, 4) ise, satır içi olmayan bir ad alanı kullanmanın sağlayabileceği aynı yararlı bağlayıcı tanı ve davranışını vermek. Bir kütüphane kullandığınızı varsayalım:

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

Kullanıcının library::foo, daha temiz görünen belgelerdeki ABI sürümünü bilmesine veya eklemesine gerek kalmadan arama yapmasına olanak tanır . Kullanmak library::abiverison129389123::fookirli görünecektir.

Bir güncelleme yapıldığında foo, yani sınıfa yeni bir üye eklendiğinde, zaten üyeyi kullanmayacakları için API düzeyinde mevcut programları etkilemez VE satır içi ad alanı adındaki değişiklik API düzeyinde hiçbir şey değiştirmez çünkü library::foohala çalışacak.

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

Bununla birlikte, onunla bağlantı kuran programlar için, satır içi ad alanı adı normal ad alanı gibi sembol adlarına karıştırıldığı için, değişiklik bağlayıcıya saydam olmaz. Bu nedenle, uygulama yeniden derlenmemişse ancak kitaplığın yeni bir sürümüyle abi_v1bağlantılıysa, ABI uyumsuzluğu nedeniyle çalışma zamanında gerçekte bağlanmak ve daha sonra gizemli bir mantık hatasına neden olmak yerine hata bulunmayan bir sembol sunar . Yeni bir üye eklemek, programı derleme zamanında etkilemese bile (API düzeyi) tür tanımındaki değişiklik nedeniyle ABI uyumluluğuna neden olur.

Bu senaryoda:

namespace library {
    namespace abi_v1 {
        class foo {
        } 
    }

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

Satır içi 2 olmayan ad alanı kullanmak gibi, uygulamanın yeniden derlenmesine gerek kalmadan kütüphanenin yeni bir sürümünün bağlanmasına izin verir, çünkü abi_v1genel sembollerden birinde karıştırılır ve doğru (eski) tür tanımını kullanır. Ancak uygulamanın yeniden derlenmesi, başvuruların çözümlenmesine neden olur library::abi_v2.

Kullanmak using namespace, kullanmaktan daha az işlevseldir inline(hat dışı tanımlar çözülmez), ancak yukarıdaki ile aynı 4 avantajı sağlar. Ancak asıl soru, şimdi bunu yapmak için özel bir anahtar kelime olduğunda neden bir geçici çözüm kullanmaya devam etmektir. Daha iyi uygulama, daha az ayrıntılı (2 yerine 1 kod satırını değiştirmek zorunda) ve niyeti açıklığa kavuşturuyor.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.