Adsız / anonim ad alanları ve statik işlevler


508

C ++ 'ın bir özelliği, şöyle adlandırılmamış (anonim) ad alanları oluşturma yeteneğidir:

namespace {
    int cannotAccessOutsideThisFile() { ... }
} // namespace

Böyle bir özelliğin işe yaramayacağını düşünürsünüz - ad alanının adını belirleyemediğiniz için, içerideki herhangi bir şeye dışarıdan erişmek imkansızdır. Fakat bu isimsiz ad alanları vardır onlara örtük kullanılarak-maddesini varmış gibi, onlar oluşturulan dosyanın içinde erişilebilir.

Benim sorum şu, neden veya ne zaman bu statik işlevleri kullanmak için tercih edilir? Yoksa tam olarak aynı şeyi yapmanın iki yolu mu?


13
C ++ 11'de staticbu bağlamda kullanımı tanımlanmamıştır ; Her ne kadar adı açıklanmayan ad için üstün bir alternatiftirstatic , ordasın zaman başarısız nerede örneklerini statickurtarmaya gelir .
legends2k

Yanıtlar:


332

C ++ Standardı, bölüm 7.3.1.1 Adsız ad alanları, paragraf 2'de okur:

Bir ad alanı kapsamındaki nesneler bildirilirken, statik anahtar kelimenin kullanımı kullanımdan kaldırılır, adsız ad alanı üstün bir alternatif sağlar.

Statik, bildirimlerin yazılması için değil, yalnızca nesnelerin, işlevlerin ve anonim birliklerin adları için geçerlidir.

Düzenle:

Statik anahtar kelimenin bu kullanımından kaldırılma kararı (bir çeviri birimindeki değişken bildirimin görünürlüğünü etkileme) tersine çevrildi ( ref ). Bu durumda, statik veya adsız bir ad alanı kullanmak, aslında aynı şeyi yapmanın iki yolu haline gelmiştir. Daha fazla tartışma için lütfen bu SO sorusuna bakın.

Adsız ad alanları, çeviri birimi yerel türlerini tanımlamanıza izin verme avantajına sahiptir. Daha fazla ayrıntı için lütfen bu SO sorusuna bakın.

Kredi dikkatimi çektiği için Mike Percy'ye gidiyor .


39
Head Geek, yalnızca işlevlere karşı kullanılan statik anahtar kelimeyi sorar. Ad alanı kapsamında bildirilen varlığa uygulanan statik anahtar kelime, dahili bağlantısını belirtir. Anonim ad alanında bildirilen varlığın harici bağlantısı (C ++ / 3.5) vardır, ancak benzersiz olarak adlandırılmış bir kapsamda yaşadığı garanti edilir. Adsız ad alanının bu anonimliği, bildirimini yalnızca bir çeviri biriminden erişilebilir kılan etkin bir şekilde gizler. İkincisi, statik anahtar kelimeyle aynı şekilde çalışır.
mloskot

5
dış bağlantının dezavantajı nedir? Bu satırlamayı etkiler mi?
Alex

17
Statik anahtar kelimenin kullanımdan kaldırıldığını söyleyen C ++ tasarım komitesinde olanlar muhtemelen büyük bir gerçek dünya sisteminde büyük bir C kodu ile çalışmadılar ... blok).
Calmarius

23
Bu yanıt Google'da "c ++ anonim ad alanı" için en iyi sonuç olarak geldiğinden, statik kullanımın artık kullanımdan kaldırılmadığına dikkat edilmelidir. Daha fazla bilgi için stackoverflow.com/questions/4726570/… ve open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1012 adresine bakın .
Michael Percy

2
@ErikAronesty Kulağa yanlış geliyor. Tekrarlanabilir bir örneğiniz var mı? C ++ 11'den itibaren - ve hatta daha önce bazı derleyicilerde - isimsiz namespaces dolaylı olarak iç bağlantıya sahiptir, bu yüzden fark olmamalıdır. Daha önce zayıf ifadelerden kaynaklanabilecek herhangi bir sorun, bu C ++ 11'de bir gereksinim haline getirilerek çözüldü.
underscore_d

73

Yöntemleri anonim bir ad alanına koymak, Tek Tanımlama Kuralını yanlışlıkla ihlal etmenizi önleyerek yardımcı yöntemlerinizi bağlayabileceğiniz diğer yöntemlerle aynı şekilde adlandırmanız konusunda asla endişelenmenize izin vermez.

Ve luke tarafından işaret edildiği gibi, standart tarafından statik üyelere göre anonim ad alanları tercih edilir.


2
Statik üye işlevlerine değil, statik bağımsız işlevlere (yani dosya kapsamındaki işlevlere) atıfta bulunuyordum. Statik bağımsız işlevler, adsız bir ad alanındaki işlevlerle aynıdır, bu nedenle soru.
Kafa Geek

2
Ah; ODR hala geçerlidir. Paragraf kaldırılmak üzere düzenlendi.
hazzen

aldığımda, statik bir işlev için ODR üstbilgide tanımlandığında çalışmıyor ve bu üstbilgi birden fazla çeviri birimine dahil edildi, değil mi? bu durumda aynı işlevin birden çok kopyasını alırsınız
Andriy Tylychko

@Andy T: Eklenen başlık için "çoklu tanımları" gerçekten görmüyorsunuz. Önişlemci bununla ilgilenir. Ön işlemcinin ürettiği çıktıyı incelemeye ihtiyaç olmadığı sürece, bu bana oldukça egzotik ve nadir görünüyor. Ayrıca, "#ifndef SOME_GUARD - #define SOME_GUARD ..." gibi başlık dosyalarına "korumalar" eklemek için iyi bir uygulama vardır, bu da önişlemcinin iki kez aynı üstbilgiyi eklemesini önler.
Nikita Vorontsov

@NikitaVorontsov muhafız, aynı başlığın aynı çeviri birimine eklenmesini engelleyebilir, ancak farklı çeviri birimlerinde birden fazla tanımlamaya izin verir. Bu, satırda "çoklu tanım" bağlayıcı hatasına neden olabilir.
Alex

37

Statikin şaşırtıcı bir etkiye sahip olduğu bir kenar durumu vardır (en azından benim içindi). 14.6.4.2/1'deki C ++ 03 Standardı:

Şablon parametresine bağlı bir işlev çağrısı için, işlev adı nitelenmemiş bir kimlikse ancak bir şablon kimliği değilse , aday işlevler, olağan arama kuralları (3.4.1, 3.4.2) kullanılarak bulunur:

  • Aramanın nitelenmemiş ad araması (3.4.1) kullanan kısmı için, yalnızca şablon tanımı bağlamından dış bağlantısı olan işlev bildirimleri bulunur.
  • Aramanın ilişkili ad alanlarını (3.4.2) kullanan kısmı için, yalnızca şablon tanımı bağlamında veya şablon örnekleme bağlamında bulunan harici bağlantıya sahip işlev bildirimleri bulunur.

...

Aşağıdaki kod arayabilir foo(void*)ve foo(S const &)beklediğiniz gibi değil .

template <typename T>
int b1 (T const & t)
{
  foo(t);
}

namespace NS
{
  namespace
  {
    struct S
    {
    public:
      operator void * () const;
    };

    void foo (void*);
    static void foo (S const &);   // Not considered 14.6.4.2(b1)
  }

}

void b2()
{
  NS::S s;
  b1 (s);
}

Kendi içinde bu muhtemelen büyük bir anlaşma değil, ama tam uyumlu bir C ++ derleyicisi (yani destekli bir export) için staticanahtar kelimenin hala başka hiçbir şekilde mevcut olmayan işlevselliğe sahip olacağını vurgulamaktadır.

// bar.h
export template <typename T>
int b1 (T const & t);

// bar.cc
#include "bar.h"
template <typename T>
int b1 (T const & t)
{
  foo(t);
}

// foo.cc
#include "bar.h"
namespace NS
{
  namespace
  {
    struct S
    {
    };

    void foo (S const & s);  // Will be found by different TU 'bar.cc'
  }
}

void b2()
{
  NS::S s;
  b1 (s);
}

Adsız ad alanımızdaki işlevin ADL kullanan şablonlarda bulunmamasını sağlamanın tek yolu, bunu yapmaktır static.

Modern C ++ Güncelleştirmesi

C ++ '11 itibariyle, adsız bir ad alanının üyeleri dolaylı olarak iç bağlantıya sahiptir (3.5 / 4):

Adsız bir ad alanı veya adsız bir ad alanı içinde doğrudan veya dolaylı olarak bildirilen bir ad alanı dahili bağlantıya sahiptir.

Ancak aynı zamanda, bağlantıdan bahsetmek için 14.6.4.2/1 güncellendi (bu C ++ '14'ten alınmıştır):

Postfix-ifadesinin bağımlı bir isim olduğu bir işlev çağrısı için, aday işlevler, aşağıdakiler dışında olağan arama kuralları (3.4.1, 3.4.2) kullanılarak bulunur:

  • Aramanın nitelenmemiş ad araması (3.4.1) kullanan kısmı için, yalnızca şablon tanımı bağlamından işlev bildirimleri bulunur.

  • Aramanın ilişkili ad alanlarını (3.4.2) kullanan kısmı için, yalnızca şablon tanımı bağlamında veya şablon örnekleme bağlamında bulunan işlev bildirimleri bulunur.

Sonuç, statik ve adsız ad alanı üyeleri arasındaki bu farkın artık mevcut olmamasıdır.


3
Dışa aktarma anahtar kelimesinin soğuk ölü olması gerekmez mi? "İhracatı" destekleyen tek derleyiciler deneyseldir ve sürprizler olmadıkça, "ihracat" beklenmedik yan etkiler nedeniyle başkalarına bile uygulanmayacaktır (yapmamak da beklenmiyordu)
paercebal

2
Herb Sutter'in subjet'teki makalesine bakın: gotw.ca/publications/mill23-x.htm
paercebal

3
Edison Tasarım Grubu'nun (EDG) ön ucu deneysel olmaktan başka bir şey değil. Neredeyse kesinlikle dünyadaki en standart C ++ uygulamasıdır. Intel C ++ derleyicisi EDG kullanır.
Richard Corden

1
Hangi C ++ özelliğinin 'beklenmeyen yan etkileri' yoktur? Dışa aktarma durumunda, farklı bir TU'dan adlandırılmamış bir ad alanı işlevi bulunacaktır - bu, şablon tanımını doğrudan eklediğinizle aynıdır. Böyle olmasaydı daha şaşırtıcı olurdu!
Richard Corden

Bence orada bir yazım hatası var - NS::Sçalışmak için içeride olmanıza Sgerek yok namespace {}mu?
Eric

12

Son zamanlarda kodumda anonim ad alanlarıyla statik anahtar sözcükleri değiştirmeye başladım, ancak hemen ad alanındaki değişkenlerin artık hata ayıklayıcımda inceleme için mevcut olmadığı bir soruna rastladım. VC60 kullanıyordum, bu yüzden diğer hata ayıklayıcılarla ilgili bir sorun olup olmadığını bilmiyorum. Benim geçici çözüm bir 'modül' ad alanı tanımlamak oldu, burada cpp dosyama adını verdi.

Örneğin, XmlUtil.cpp dosyamda, XmlUtil_I { ... }tüm modül değişkenlerim ve işlevlerim için bir ad alanı tanımladım . Bu şekilde XmlUtil_I::değişkenlere erişmek için hata ayıklayıcıdaki kalifikasyonu uygulayabilirim . Bu durumda, başka bir yerde kullanmak isteyebileceğim _Igibi genel bir ad alanından ayırır XmlUtil.

Bu yaklaşımın gerçekten anonim olana kıyasla potansiyel bir dezavantajı, birisinin diğer modüllerde ad alanı niteleyicisini kullanarak istenen statik kapsamı ihlal edebileceğidir. Bunun büyük bir endişe olup olmadığını bilmiyorum.


7
Ben de bunu yaptım, ama #if DEBUG namespace BlahBlah_private { #else namespace { #endif, böylece, "modül ad alanı" sadece hata ayıklama yapılarında mevcuttur ve gerçek anonim ad alanı aksi takdirde kullanılır. Hata ayıklayıcıların bununla başa çıkmak için güzel bir yol vermesi güzel olurdu. Doxygen de bununla karışıyor.
Kristopher Johnson

4
adlandırılmamış ad alanı gerçekten statik için uygun bir yedek değildir. statik "gerçekten TU asla ouside bağlı alır" anlamına gelir. adsız ad alanı, "TU dışındaki bir ana sınıftan çağrılması durumunda" rastgele bir ad olarak hala dışa
aktarılır

7

Bu amaçla statik anahtar kelime kullanımı C ++ 98 standardı tarafından kullanımdan kaldırılmıştır. Statik problem, tip tanımına uygulanmamasıdır. Ayrıca, farklı bağlamlarda farklı şekillerde kullanılan aşırı yüklenmiş bir anahtar kelime olduğundan, adsız ad alanları işleri biraz basitleştirir.


1
Bir türü yalnızca tek bir çeviri biriminde kullanmak istiyorsanız .cpp dosyasının içinde bildirin. Yine de diğer çeviri birimlerinden erişilemez.
Calmarius

4
Düşünürdün, değil mi? Ancak aynı uygulamadaki başka bir çeviri birimi (= cpp-file) aynı ada sahip bir tür bildirdiyse, hata ayıklaması oldukça zor sorunlar içindesiniz :-). Örneğin, diğerlerinden yöntem çağrılırken, türlerden biri için vtable'ın kullanıldığı durumlarla karşılaşabilirsiniz.
avl_sweden

1
Artık kullanılmıyor. Ve tip defs ihraç edilmez, bu anlamsızdır. statik bağımsız işlevler ve global değişkenler için yararlıdır. adsız ad alanları sınıflar için kullanışlıdır.
Erik Aronesty

6

Deneyimden, anonim ad alanına eski statik işlevleri koymak için C ++ yolu olsa da, eski derleyicilerin bazen bu konuda sorun yaşayabileceğini unutmayın. Şu anda hedef platformlarımız için birkaç derleyici ile çalışıyorum ve daha modern Linux derleyicisi, işlevleri anonim ad alanına yerleştirmekle sorun değil.

Ancak, gelecekte belirtilmeyen bir sürüm yayınlanıncaya kadar beklediğimiz Solaris üzerinde çalışan eski bir derleyici bazen kabul edecek ve bazen de hata olarak işaretleyecektir. Hata beni endişelendiren şey değil, kabul ettiğinde ne yapıyor olabilir . Bu yüzden tahtada modern olana kadar, anonim ad alanını tercih ettiğimiz statik (genellikle sınıf kapsamındaki) işlevleri kullanıyoruz.


3

Ayrıca, bu örnek gibi bir değişkende statik anahtar kelime kullanılıyorsa:

namespace {
   static int flag;
}

Eşleme dosyasında görülmez


7
O zaman anonim bir isim alanına ihtiyacınız yoktur.
Calmarius

2

Anonim ad alanları ve statik işlevler arasında derleyiciye özgü bir fark, aşağıdaki kodu derlerken görülebilir.

#include <iostream>

namespace
{
    void unreferenced()
    {
        std::cout << "Unreferenced";
    }

    void referenced()
    {
        std::cout << "Referenced";
    }
}

static void static_unreferenced()
{
    std::cout << "Unreferenced";
}

static void static_referenced()
{
    std::cout << "Referenced";
}

int main()
{
    referenced();
    static_referenced();
    return 0;
}

Bu kodu VS 2017 ile derlemek (C4505 uyarısını etkinleştirmek için seviye 4 uyarı bayrağını / W4'ü belirtmek: referanslandırılmamış yerel işlev kaldırıldı ) ve -Wunused işlevi veya -Wall bayrağıyla gcc 4.9, VS 2017'nin yalnızca kullanılmayan statik fonksiyon. gcc 4.9 ve üstü, clang 3.3 ve üstü, isim alanındaki referanssız fonksiyon için uyarılar ve kullanılmayan statik fonksiyon için bir uyarı verecektir.

Gcc 4.9 ve MSVC 2017 canlı demosu


2

Şahsen ben aşağıdaki nedenlerden dolayı isimsiz isim alanlarına göre statik fonksiyonları tercih ediyorum:

  • Yalnızca işlev tanımından derlendiği çeviri birimine özel olduğu açık ve nettir. Adsız ad alanı ile, bir işlevin ad alanında olup olmadığını görmek için ekranı kaydırıp aramanız gerekebilir.

  • Ad alanlarındaki işlevler, bazı (eski) derleyiciler tarafından extern olarak değerlendirilebilir. VS2017'de hala extern. Bu nedenle, bir işlev adsız ad alanında olsa bile, bunları statik olarak işaretlemek isteyebilirsiniz.

  • Statik işlevler C veya C ++ 'da çok benzer davranırken, isimsiz ad alanları sadece C ++' dır. isimsiz ad alanları da girinti ekstra seviye eklemek ve ben bunu sevmiyorum :)

Bu nedenle, işlevler için statik kullanımının artık kullanılmadığını görmekten mutluluk duyuyorum .


Anonim ad alanlarındaki işlevlerin dış bağlantıya sahip olması gerekir. Onları benzersiz kılmak için sadece karışık. Yalnızca staticanahtar sözcük bir işleve yerel bağlantı uygular. Ayrıca, kesinlikle sadece çılgın bir deli, ad alanları için girinti ekleyebilir mi?
Roflcopter4

0

Bu özelliği yalnızca şimdi sorunuzu okurken öğrendikten sonra sadece spekülasyon yapabilirim. Bu, dosya düzeyinde bir statik değişkene göre çeşitli avantajlar sağlıyor gibi görünüyor:

  • Anonim ad alanları, sembollerin kaçamayacağı birden fazla koruma düzeyi sağlayarak iç içe yerleştirilebilir.
  • Aynı kaynak dosyaya birkaç anonim ad alanı yerleştirilebilir ve böylece aynı dosya içinde farklı statik düzey kapsamları oluşturulabilir.

Herkes gerçek kodda anonim ad alanları kullandıysanız öğrenmek istiyorum.


4
İyi spekülasyonlar, ama yanlış. Bu ad alanlarının kapsamı dosya geniştir.
Konrad Rudolph

Tam olarak doğru değil, başka bir ad alanı içinde adsız bir ad alanı tanımlarsanız, yine de yalnızca dosya geniştir ve yalnızca bu ad alanında olduğu görülebilir. Dene.
Greg Rogers

Yanlış olabilir ama, hayır, sanırım dosya çapında değil: Sadece anonim ad alanından sonra kod erişilebilir . Bu ince bir şeydir ve genellikle bir kaynağı birden çok isimsiz ad alanı ile kirletmek istemem ... Yine de, bunun kullanımları olabilir.
paercebal

0

Fark, karıştırılmış tanımlayıcının adıdır ( _ZN12_GLOBAL__N_11bEvs _ZL1b, gerçekten önemli değildir, ancak her ikisi de sembol tablosundaki yerel sembollere birleştirilir ( .globalasm yönergesinin yokluğu ).

#include<iostream>
namespace {
   int a = 3;
}

static int b = 4;
int c = 5;

int main (){
    std::cout << a << b << c;
}

        .data
        .align 4
        .type   _ZN12_GLOBAL__N_11aE, @object
        .size   _ZN12_GLOBAL__N_11aE, 4
_ZN12_GLOBAL__N_11aE:
        .long   3
        .align 4
        .type   _ZL1b, @object
        .size   _ZL1b, 4
_ZL1b:
        .long   4
        .globl  c
        .align 4
        .type   c, @object
        .size   c, 4
c:
        .long   5
        .text

Yuvalanmış anonim bir ad alanına gelince:

namespace {
   namespace {
       int a = 3;
    }
}

        .data
        .align 4
        .type   _ZN12_GLOBAL__N_112_GLOBAL__N_11aE, @object
        .size   _ZN12_GLOBAL__N_112_GLOBAL__N_11aE, 4
_ZN12_GLOBAL__N_112_GLOBAL__N_11aE:
        .long   3
        .align 4
        .type   _ZL1b, @object
        .size   _ZL1b, 4

Çeviri birimindeki tüm 1. düzey anonim ad alanları birbirleriyle birleştirilir, Çeviri birimindeki tüm 2. düzey iç içe anonim ad alanları birbirleriyle birleştirilir

Anonim bir ad alanında iç içe (satır içi) ad alanınız da olabilir

namespace {
   namespace A {
       int a = 3;
    }
}

        .data
        .align 4
        .type   _ZN12_GLOBAL__N_11A1aE, @object
        .size   _ZN12_GLOBAL__N_11A1aE, 4
_ZN12_GLOBAL__N_11A1aE:
        .long   3
        .align 4
        .type   _ZL1b, @object
        .size   _ZL1b, 4

which for the record demangles as:
        .data
        .align 4
        .type   (anonymous namespace)::A::a, @object
        .size   (anonymous namespace)::A::a, 4
(anonymous namespace)::A::a:
        .long   3
        .align 4
        .type   b, @object
        .size   b, 4

Ayrıca anonim satır içi ad alanlarına sahip olabilirsiniz, ancak anlayabildiğim kadarıyla inlineanonim bir ad alanında 0 etkisi vardır

inline namespace {
   inline namespace {
       int a = 3;
    }
}

_ZL1b: _Zbunun karışık bir tanımlayıcı olduğu anlamına gelir. Lanlamına gelen yerel bir semboldür static. 1tanımlayıcının bve ardından tanımlayıcının uzunluğudurb

_ZN12_GLOBAL__N_11aE _Zbunun karıştırılmış bir tanımlayıcı olduğu anlamına gelir. NBu ad olduğu anlamına gelir 12anonim ad adı uzunluğu _GLOBAL__N_1, daha sonra anonim isim alanı _GLOBAL__N_1, daha sonra 1tanımlayıcının uzunluğu a, atanımlayıcıdır ave Etanımlayıcı kapanır bir isim alanı bulunur.

_ZN12_GLOBAL__N_11A1aE içinde başka bir ad alanı düzeyi olması dışında yukarıdakiyle aynıdır 1A

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.