Ruang nama tanpa nama / anonim vs. fungsi statis

508

Fitur C ++ adalah kemampuan untuk membuat ruang nama tanpa nama (anonim), seperti:

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

Anda akan berpikir bahwa fitur seperti itu akan sia-sia - karena Anda tidak dapat menentukan nama namespace, tidak mungkin untuk mengakses apa pun di dalamnya dari luar. Tetapi ruang nama yang tidak disebutkan namanya ini dapat diakses di dalam file tempat mereka dibuat, seolah-olah Anda memiliki klausa penggunaan implisit kepadanya.

Pertanyaan saya adalah, mengapa atau kapan ini lebih disukai daripada menggunakan fungsi statis? Atau apakah mereka pada dasarnya dua cara melakukan hal yang persis sama?

Kepala Geek
sumber
13
Dalam C ++ 11 penggunaan staticdalam konteks ini tidak dihargai ; meskipun namespace yang tidak disebutkan namanya adalah alternatif yang unggulstatic , ada contoh di mana ia gagal ketika staticdatang untuk menyelamatkan .
legends2k

Jawaban:

332

Standar C ++ membaca di bagian 7.3.1.1 Ruang nama yang tidak disebutkan namanya, paragraf 2:

Penggunaan kata kunci statis tidak digunakan lagi ketika mendeklarasikan objek dalam lingkup namespace, unnamed-namespace memberikan alternatif yang unggul.

Statis hanya berlaku untuk nama objek, fungsi, dan serikat anonim, bukan untuk mengetik deklarasi.

Edit:

Keputusan untuk menghentikan penggunaan kata kunci statis ini (memengaruhi visibilitas deklarasi variabel dalam unit terjemahan) telah dibatalkan ( ref ). Dalam hal ini menggunakan namespace statis atau tanpa nama kembali ke dasarnya dua cara melakukan hal yang sama persis. Untuk diskusi lebih lanjut silakan lihat pertanyaan SO ini .

Ruang nama yang tidak disebutkan namanya masih memiliki keuntungan memungkinkan Anda untuk menentukan jenis terjemahan-unit-lokal. Silakan lihat pertanyaan SO ini untuk lebih jelasnya.

Penghargaan diberikan kepada Mike Percy karena telah menyampaikan ini kepada saya.

Lukas
sumber
39
Head Geek bertanya tentang kata kunci statis yang digunakan terhadap fungsi saja. Kata kunci statis yang diterapkan pada entitas yang dinyatakan dalam lingkup namespace menentukan kaitan internalnya. Entitas yang dideklarasikan dalam namespace anonim memiliki tautan eksternal (C ++ / 3.5) namun dijamin hidup dalam lingkup yang bernama unik. Anonimitas namespace tanpa nama ini secara efektif menyembunyikan deklarasi sehingga membuatnya hanya dapat diakses dari dalam unit terjemahan. Yang terakhir efektif bekerja dengan cara yang sama dengan kata kunci statis.
mloskot
5
apa kelemahan hubungan eksternal? Bisakah ini memengaruhi inlining?
Alex
17
Orang-orang di komite desain C ++ yang mengatakan kata kunci statis sudah usang mungkin tidak pernah bekerja dengan kode C besar dalam sistem dunia nyata besar ... (Anda segera melihat kata kunci statis tetapi bukan ruang nama anonim jika berisi banyak deklarasi dengan komentar besar blok.)
Calmarius
23
Karena jawaban ini muncul di Google sebagai hasil teratas untuk "c ++ anonymous namespace", perlu dicatat bahwa penggunaan statis tidak lagi ditinggalkan. Lihat stackoverflow.com/questions/4726570/… dan open-std.org/jtc1/sc22/wg21/docs/cwg_defects.html#1012 untuk informasi lebih lanjut.
Michael Percy
2
@ErikAronesty Kedengarannya salah. Apakah Anda memiliki contoh yang dapat direproduksi? Pada C ++ 11 - dan bahkan sebelum itu di beberapa kompiler - tanpa nama namespaces secara implisit memiliki hubungan internal, jadi seharusnya tidak ada perbedaan. Setiap masalah yang sebelumnya mungkin muncul dari kata-kata yang buruk diselesaikan dengan menjadikan ini persyaratan dalam C ++ 11.
underscore_d
73

Menempatkan metode dalam ruang nama anonim mencegah Anda dari melanggar Aturan Satu Definisi , memungkinkan Anda untuk tidak pernah khawatir tentang penamaan metode pembantu Anda sama seperti beberapa metode lain yang mungkin Anda tautkan.

Dan, seperti yang ditunjukkan oleh Lukas, ruang nama anonim lebih disukai oleh standar daripada anggota statis.

hazzen
sumber
2
Saya mengacu pada fungsi statis yang berdiri sendiri (yaitu fungsi file-scoped), bukan fungsi anggota statis. Fungsi statis yang berdiri sendiri hampir sama dengan fungsi dalam ruang nama yang tidak disebutkan namanya, demikian pertanyaannya.
Kepala Geek
2
Ah; nah, ODR masih berlaku. Diedit untuk menghapus paragraf.
hazzen
seperti yang saya dapatkan, ODR untuk fungsi statis tidak berfungsi ketika didefinisikan di header dan header ini dimasukkan ke dalam lebih dari satu unit terjemahan, bukan? dalam hal ini Anda menerima beberapa salinan dari fungsi yang sama
Andriy Tylychko
@Andy T: Anda tidak benar-benar melihat "beberapa definisi" dalam hal header disertakan. Preprocessor menanganinya. Kecuali ada kebutuhan dalam mempelajari output yang telah dihasilkan preprocessor, yang bagi saya terlihat agak eksotis dan langka. Juga ada praktik yang baik untuk memasukkan "penjaga" dalam file header, seperti: "#ifndef SOME_GUARD - #define SOME_GUARD ..." yang seharusnya mencegah preprocessor dari memasukkan header yang sama dua kali.
Nikita Vorontsov
@NikitaVorontsov penjaga dapat mencegah memasukkan header yang sama ke dalam unit terjemahan yang sama, namun memungkinkan beberapa definisi dalam unit terjemahan yang berbeda. Ini mungkin menyebabkan kesalahan "beberapa definisi" di baris bawah.
Alex
37

Ada satu kasus tepi di mana statis memiliki efek yang mengejutkan (setidaknya bagi saya). Standar C ++ 03 menyatakan dalam 14.6.4.2/1:

Untuk panggilan fungsi yang tergantung pada parameter templat, jika nama fungsi adalah id yang tidak memenuhi syarat tetapi bukan id template , fungsi kandidat ditemukan menggunakan aturan pencarian biasa (3.4.1, 3.4.2) kecuali bahwa:

  • Untuk bagian pencarian menggunakan pencarian nama yang tidak memenuhi syarat (3.4.1), hanya deklarasi fungsi dengan tautan eksternal dari konteks definisi templat yang ditemukan.
  • Untuk bagian pencarian menggunakan ruang nama terkait (3.4.2), hanya deklarasi fungsi dengan tautan eksternal yang ditemukan dalam konteks definisi templat atau konteks instantiasi templat yang ditemukan.

...

Kode di bawah ini akan memanggil foo(void*)dan bukan foo(S const &)seperti yang Anda harapkan.

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);
}

Dalam dirinya sendiri ini mungkin bukan masalah besar, tetapi itu menyoroti bahwa untuk kompiler C ++ sepenuhnya kompatibel (yaitu satu dengan dukungan untuk export) statickata kunci masih akan memiliki fungsi yang tidak tersedia dengan cara lain.

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

Satu-satunya cara untuk memastikan bahwa fungsi di namespace kami yang tidak disebutkan namanya tidak akan ditemukan dalam template menggunakan ADL adalah dengan membuatnya static.

Pembaruan untuk C ++ Modern

Pada C ++ '11, anggota namespace yang tidak disebutkan namanya memiliki hubungan internal secara implisit (3.5 / 4):

Namespace tanpa nama atau namespace dideklarasikan secara langsung atau tidak langsung dalam namespace yang tidak disebutkan namanya memiliki tautan internal.

Tetapi pada saat yang sama, 14.6.4.2/1 telah diperbarui untuk menghapus penyebutan tautan (ini diambil dari C ++ '14):

Untuk panggilan fungsi di mana ekspresi-postfix adalah nama dependen, fungsi kandidat ditemukan menggunakan aturan pencarian biasa (3.4.1, 3.4.2) kecuali bahwa:

  • Untuk bagian pencarian menggunakan pencarian nama yang tidak memenuhi syarat (3.4.1), hanya deklarasi fungsi dari konteks definisi template yang ditemukan.

  • Untuk bagian pencarian menggunakan ruang nama terkait (3.4.2), hanya deklarasi fungsi yang ditemukan dalam konteks definisi templat atau konteks instantiasi templat yang ditemukan.

Hasilnya adalah bahwa perbedaan khusus antara anggota namespace statis dan tidak bernama tidak ada lagi.

Richard Corden
sumber
3
Bukankah kata kunci ekspor seharusnya mati dingin? Satu-satunya penyusun yang mendukung "ekspor" adalah yang eksperimental, dan kecuali kejutan, "ekspor" bahkan tidak akan diterapkan pada yang lain karena efek samping yang tidak diharapkan (selain tidak diharapkan memang diharapkan)
paercebal
2
Lihat artikel Herb Sutter tentang subjet: gotw.ca/publications/mill23-x.htm
paercebal
3
Bagian depan dari Edison Design Group (EDG) tidak lain adalah eksperimental. Ini hampir pasti implementasi C ++ yang paling standar yang sesuai di dunia. Kompiler Intel C ++ menggunakan EDG.
Richard Corden
1
Fitur C ++ apa yang tidak memiliki 'efek samping tak terduga'? Dalam kasus ekspor, fungsi namespace yang tidak disebutkan namanya akan ditemukan dari TU yang berbeda - yaitu sama seperti jika Anda memasukkan definisi template secara langsung. Akan lebih mengejutkan jika tidak seperti ini!
Richard Corden
Saya pikir Anda memiliki kesalahan ketik di sana - untuk NS::Sbekerja, tidak Sperlu tidak ada di dalam namespace {}?
Eric
12

Saya baru-baru ini mulai mengganti kata kunci statis dengan ruang nama anonim dalam kode saya tetapi segera mengalami masalah di mana variabel dalam namespace tidak lagi tersedia untuk diperiksa di debugger saya. Saya menggunakan VC60, jadi saya tidak tahu apakah itu bukan masalah dengan debugger lain. Solusi saya adalah mendefinisikan namespace 'modul', di mana saya memberinya nama file cpp saya.

Misalnya, dalam file XmlUtil.cpp saya, saya mendefinisikan namespace XmlUtil_I { ... }untuk semua variabel dan fungsi modul saya. Dengan begitu saya bisa menerapkan XmlUtil_I::kualifikasi di debugger untuk mengakses variabel. Dalam hal ini, _Imembedakannya dari namespace publik seperti XmlUtilyang mungkin ingin saya gunakan di tempat lain.

Saya kira kelemahan potensial dari pendekatan ini dibandingkan dengan yang benar-benar anonim adalah bahwa seseorang dapat melanggar ruang lingkup statis yang diinginkan dengan menggunakan kualifikasi namespace di modul lain. Saya tidak tahu apakah itu merupakan perhatian utama.

Evg
sumber
7
Saya sudah melakukan ini juga, tetapi dengan #if DEBUG namespace BlahBlah_private { #else namespace { #endif, jadi "module namespace" hanya ada di debug builds dan namespace anonim true digunakan sebaliknya. Alangkah baiknya jika debuggers memberi cara yang bagus untuk menangani ini. Doxygen juga bingung karenanya.
Kristopher Johnson
4
namespace tanpa nama sebenarnya bukan pengganti statis. statis berarti "benar-benar ouside TU ini tidak pernah terhubung". namespace tanpa nama berarti "masih diekspor, sebagai nama acak, kalau-kalau dipanggil dari kelas induk yang berada di luar TU" ...
Erik Aronesty
7

Penggunaan kata kunci statis untuk tujuan itu sudah usang dengan standar C ++ 98. Masalah dengan statis adalah bahwa itu tidak berlaku untuk definisi tipe. Itu juga kata kunci kelebihan beban yang digunakan dalam berbagai cara dalam konteks yang berbeda, jadi ruang nama yang tidak disebutkan namanya menyederhanakan sedikit hal.

Firas Assaad
sumber
1
Jika Anda ingin menggunakan tipe hanya dalam satu unit terjemahan, maka nyatakan di dalam file .cpp. Itu tidak akan dapat diakses dari unit terjemahan lain.
Calmarius
4
Anda akan berpikir, bukan? Tetapi jika unit terjemahan lain (= cpp-file) dalam aplikasi yang sama pernah menyatakan jenis dengan nama yang sama, Anda berada dalam masalah yang agak sulit di-debug :-). Misalnya, Anda mungkin berakhir dengan situasi di mana vtable untuk salah satu tipe digunakan ketika memanggil metode di sisi lain.
avl_sweden
1
Tidak ditinggalkan lagi. Dan def jenis tidak diekspor, jadi itu tidak ada artinya. statika berguna untuk fungsi mandiri dan vars global. ruang nama tanpa nama berguna untuk kelas.
Erik Aronesty
6

Dari pengalaman saya hanya akan mencatat bahwa sementara itu adalah cara C ++ untuk menempatkan fungsi-fungsi yang sebelumnya statis ke dalam namespace anonim, kompiler lama kadang-kadang dapat memiliki masalah dengan ini. Saat ini saya bekerja dengan beberapa kompiler untuk platform target kami, dan kompiler Linux yang lebih modern baik-baik saja dengan menempatkan fungsi ke dalam namespace anonim.

Tetapi kompiler lama yang berjalan pada Solaris, yang kita nantikan sampai rilis mendatang yang tidak ditentukan, kadang-kadang akan menerimanya, dan di lain waktu menandainya sebagai kesalahan. Kesalahannya bukan yang membuat saya khawatir, itu apa yang mungkin dilakukannya ketika menerimanya . Jadi sampai kita menjadi modern secara keseluruhan, kita masih menggunakan fungsi statis (biasanya kelas-lingkup) di mana kita lebih suka namespace anonim.

Don Wakefield
sumber
3

Selain itu jika seseorang menggunakan kata kunci statis pada variabel seperti contoh ini:

namespace {
   static int flag;
}

Itu tidak akan terlihat di file pemetaan

Chris
sumber
7
Maka Anda tidak perlu namespace anonim sama sekali.
Calmarius
2

Perbedaan spesifik kompiler antara ruang nama anonim dan fungsi statis dapat dilihat dengan mengkompilasi kode berikut.

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

Kompilasi kode ini dengan VS 2017 (menentukan flag peringatan level 4 / W4 untuk mengaktifkan peringatan C4505: fungsi lokal yang tidak direferensikan telah dihapus ) dan gcc 4.9 dengan fungsi -Wunused-function atau -Wall menunjukkan bahwa VS 2017 hanya akan menghasilkan peringatan untuk fungsi statis yang tidak digunakan. gcc 4.9 dan lebih tinggi, serta dentang 3.3 dan lebih tinggi, akan menghasilkan peringatan untuk fungsi yang tidak direferensikan dalam namespace dan juga peringatan untuk fungsi statis yang tidak digunakan.

Demo langsung gcc 4.9 dan MSVC 2017

masrtis
sumber
2

Secara pribadi saya lebih suka fungsi statis daripada ruang nama tanpa nama untuk alasan berikut:

  • Jelas dan jelas dari definisi fungsi saja bahwa itu pribadi untuk unit terjemahan di mana ia dikompilasi. Dengan namespace tanpa nama, Anda mungkin perlu menggulir dan mencari untuk melihat apakah suatu fungsi ada dalam namespace.

  • Fungsi dalam ruang nama mungkin diperlakukan sebagai eksternal oleh beberapa kompiler (yang lebih tua). Dalam VS2017 mereka masih eksternal. Untuk alasan ini bahkan jika suatu fungsi berada dalam namespace tanpa nama, Anda mungkin masih ingin menandainya sebagai statis.

  • Fungsi statis berperilaku sangat mirip dalam C atau C ++, sedangkan ruang nama tanpa nama jelas hanya C ++. ruang nama tanpa nama juga menambahkan level tambahan dalam indentasi dan saya tidak suka itu :)

Jadi, saya senang melihat bahwa penggunaan statis untuk fungsi tidak lagi usang .

Pavel P
sumber
Fungsi dalam ruang nama anonim seharusnya memiliki tautan eksternal. Mereka hanya hancur untuk membuatnya unik. Hanya statickata kunci yang benar-benar menerapkan tautan lokal ke suatu fungsi. Juga, tentunya hanya orang gila yang mengoceh yang benar-benar akan menambahkan lekukan untuk ruang nama?
Roflcopter4
0

Setelah mempelajari fitur ini barusan saat membaca pertanyaan Anda, saya hanya bisa berspekulasi. Ini tampaknya memberikan beberapa keunggulan dibandingkan variabel statis tingkat file:

  • Ruang nama anonim dapat bersarang satu sama lain, memberikan beberapa tingkat perlindungan dari mana simbol tidak dapat melarikan diri.
  • Beberapa ruang nama anonim dapat ditempatkan di file sumber yang sama, sehingga menciptakan cakupan level statis yang berbeda di dalam file yang sama.

Saya akan tertarik mempelajari apakah ada yang menggunakan ruang nama anonim dalam kode nyata.

Commodore Jaeger
sumber
4
Spekulasi bagus, tapi salah. Ruang lingkup ruang nama ini adalah file-lebar.
Konrad Rudolph
Tidak sepenuhnya benar, jika Anda mendefinisikan namespace anonim di dalam namespace lain itu masih hanya lebar file, dan hanya dapat dilihat sebagai berada di dalam namespace itu. Cobalah.
Greg Rogers
Saya bisa saja salah tetapi, saya kira tidak, itu bukan file-lebar: Ini hanya dapat diakses oleh kode setelah namespace anonim. Ini adalah hal yang halus, dan biasanya, saya tidak ingin mencemari sumber dengan beberapa ruang nama anonim ... Namun, ini dapat memiliki kegunaan.
paercebal
0

Perbedaannya adalah nama pengidentifikasi hancur ( _ZN12_GLOBAL__N_11bEvs _ZL1b, yang tidak terlalu penting, tetapi keduanya dirakit menjadi simbol lokal dalam tabel simbol (tidak adanya .globalarahan asm).

#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

Adapun ruang nama anonim bersarang:

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

Semua ruang nama anonim tingkat 1 di unit terjemahan digabungkan satu sama lain, Semua ruang nama anonim bersarang tingkat 2 di unit terjemahan digabungkan satu sama lain

Anda juga dapat memiliki namespace bersarang (sebaris) di namespace anonim

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

Anda juga dapat memiliki ruang nama sebaris anonim, tetapi sejauh yang saya tahu, inlinepada ruang nama anonim memiliki 0 efek

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

_ZL1b: _Zberarti ini adalah pengidentifikasi yang rusak. Lberarti itu adalah simbol lokal static. 1adalah panjang pengidentifikasi bdan kemudian pengidentifikasib

_ZN12_GLOBAL__N_11aE _Zberarti ini adalah pengidentifikasi yang rusak. Nberarti ini adalah namespace 12adalah panjang nama namespace anonim _GLOBAL__N_1, maka nama namespace anonim _GLOBAL__N_1, kemudian 1panjang pengidentifikasi a, aadalah pengidentifikasi adan Emenutup pengidentifikasi yang berada di namespace.

_ZN12_GLOBAL__N_11A1aE sama seperti di atas kecuali ada level namespace lain di dalamnya 1A

Lewis Kelsey
sumber