Bagaimana Anda menggunakan ruang nama dengan benar di C ++?

231

Saya berasal dari latar belakang Java, di mana paket digunakan, bukan ruang nama. Saya terbiasa menempatkan kelas yang bekerja bersama untuk membentuk objek lengkap ke dalam paket, dan kemudian menggunakannya kembali nanti dari paket itu. Tapi sekarang saya bekerja di C ++.

Bagaimana Anda menggunakan ruang nama di C ++? Apakah Anda membuat ruang nama tunggal untuk seluruh aplikasi, atau apakah Anda membuat ruang nama untuk komponen utama? Jika demikian, bagaimana Anda membuat objek dari kelas di ruang nama lain?

Marius
sumber

Jawaban:

167

Namespaces pada dasarnya adalah paket. Mereka dapat digunakan seperti ini:

namespace MyNamespace
{
  class MyClass
  {
  };
}

Kemudian dalam kode:

MyNamespace::MyClass* pClass = new MyNamespace::MyClass();

Atau, jika Anda ingin selalu menggunakan namespace tertentu, Anda dapat melakukan ini:

using namespace MyNamespace;

MyClass* pClass = new MyClass();

Sunting: Mengikuti apa yang dikatakan bernhardrusch , saya cenderung tidak menggunakan sintaks "using namespace x" sama sekali, saya biasanya secara eksplisit menentukan namespace ketika membuat instance objek saya (yaitu contoh pertama yang saya tunjukkan).

Dan seperti yang Anda tanyakan di bawah ini , Anda dapat menggunakan ruang nama sebanyak yang Anda suka.

Tandai Ingram
sumber
25
IMO lebih baik membiasakan diri dengan awalan stdnamespace menjadi simbol daripada menggunakan usingsama sekali. Jadi saya selalu menulis std::coutatau std::stringsekarang karena saya menyebutnya sekarang. Saya tidak akan pernah menulis begitu saja cout.
Tom Savage
5
Walaupun ini sangat benar std, saya pribadi menemukan ini jauh kurang penting ketika Anda berurusan dengan perpustakaan yang lebih kecil. Seringkali Anda hanya dapat menggunakan using namespace FooBario;, terutama jika Anda menggunakan banyak jenis dari perpustakaan.
jkerian
4
@ jkerian, saya mengerti maksud Anda, tetapi saya tidak setuju karena benturan nama (dalam pikiran saya) lebih mungkin berasal dari perpustakaan kecil yang persis seperti itu. Kebanyakan orang berhati-hati untuk tidak menyebutkan kelas / fungsi yang sama dengan yang ada di STL. Yang mengatakan, saya setuju bahwa using namespace X;harus dihindari dalam file header jika memungkinkan.
Alan Turing
12
@LexFridman "Kebanyakan orang berhati-hati untuk tidak menyebutkan kelas / fungsi yang sama dengan yang ada di STL" - itu SANGAT TIDAK BENAR. Sebagai contoh jika saya menulis beberapa kode I / O yang sangat khusus untuk beberapa perangkat keras yang aneh, saya tidak akan pernah menggunakan hal lain selain mylibrary::endluntuk mewakili urutan baris baru khusus saya sendiri. Maksud saya, mengapa menemukan nama?
Kompiler saya masih tidak akan mengenali namespace, meskipun saya ingin menentukannya secara eksplisit dan saya menyertakan file yang dideklarasikan.
bgenchel
116

Untuk menghindari mengatakan semuanya, Mark Ingram telah mengatakan sedikit tip untuk menggunakan ruang nama:

Hindari direktif "using namespace" dalam file header - ini membuka namespace untuk semua bagian program yang mengimpor file header ini. Dalam file implementasi (* .cpp) ini biasanya bukan masalah besar - meskipun saya lebih suka menggunakan direktif "using namespace" pada tingkat fungsi.

Saya pikir namespaces sebagian besar digunakan untuk menghindari konflik penamaan - tidak harus mengatur struktur kode Anda. Saya akan mengatur program C ++ terutama dengan file header / struktur file.

Terkadang ruang nama digunakan dalam proyek C ++ yang lebih besar untuk menyembunyikan detail implementasi.

Catatan tambahan untuk arahan penggunaan: Beberapa orang lebih suka menggunakan "menggunakan" hanya untuk elemen tunggal:

using std::cout;  
using std::endl;
bernhardrusch
sumber
2
Salah satu keuntungan dari "menggunakan namespace" pada tingkat fungsi seperti yang Anda sarankan daripada pada tingkat file .cpp atau namespace {} di dalam .cpp adalah sangat membantu dengan pembangunan unit kompilasi-tunggal. "using namespace" adalah transitif, dan berlaku untuk namespace A melintasi diskrit namespace A {} blok dalam unit yang sama, jadi untuk unit kompilasi-tunggal Anda dengan cepat berakhir menggunakan segala sesuatu jika dilakukan pada level blok file atau namespace.
idij
using std::cout; adalah deklarasi menggunakan
Konstantin
3
Apakah mungkin menggunakan beberapa nama dari satu namespace dalam satu pernyataan? Sesuatu seperti using std::cout, std::endl;atau bahkan using std::cout, endl;,.
AlQuemist
Bisa ok untuk menggunakan using namespace xdalam header jika itu dalam namespace lain. Itu bukan sesuatu yang saya sarankan secara umum tetapi tidak mencemari namespace global.
Praxeolitic
79

Vincent Robert benar dalam komentarnya. Bagaimana Anda menggunakan ruang nama dengan benar di C ++? .

Menggunakan namespace

Namespaces paling tidak digunakan untuk membantu menghindari tabrakan nama. Di Jawa, ini diberlakukan melalui idiom "org.domain" (karena seharusnya seseorang tidak akan menggunakan apa pun selain dari nama domainnya sendiri).

Di C ++, Anda bisa memberikan namespace ke semua kode di modul Anda. Misalnya, untuk modul MyModule.dll, Anda bisa memberikan kode-nya namespace MyModule. Saya pernah melihat orang lain menggunakan MyCompany :: MyProject :: MyModule. Saya kira ini berlebihan, tetapi secara keseluruhan, tampaknya benar bagi saya.

Menggunakan "menggunakan"

Penggunaan harus digunakan dengan sangat hati-hati karena secara efektif mengimpor satu (atau semua) simbol dari namespace ke namespace Anda saat ini.

Ini jahat untuk melakukannya dalam file header karena header Anda akan mencemari setiap sumber termasuk (mengingatkan saya pada makro ...), dan bahkan dalam file sumber, gaya buruk di luar lingkup fungsi karena akan mengimpor pada lingkup global simbol dari namespace.

Cara paling aman untuk menggunakan "menggunakan" adalah dengan mengimpor simbol pilih:

void doSomething()
{
   using std::string ; // string is now "imported", at least,
                       // until the end of the function
   string a("Hello World!") ;
   std::cout << a << std::endl ;
}

void doSomethingElse()
{
   using namespace std ; // everything from std is now "imported", at least,
                       // until the end of the function
   string a("Hello World!") ;
   cout << a << endl ;
}

Anda akan melihat banyak "using namespace std;" dalam tutorial atau contoh kode. Alasannya adalah untuk mengurangi jumlah simbol untuk membuat bacaan lebih mudah, bukan karena itu adalah ide yang bagus.

"menggunakan namespace std;" tidak disarankan oleh Scott Meyers (saya tidak ingat persis buku mana, tapi saya bisa menemukannya jika perlu).

Komposisi Namespace

Ruang nama lebih dari sekedar paket. Contoh lain dapat ditemukan dalam "Bahasa Pemrograman C ++ Bjarne Stroustrup".

Dalam "Edisi Khusus", di 8.2.8 Komposisi Namespace , ia menjelaskan bagaimana Anda dapat menggabungkan dua ruang nama AAA dan BBB menjadi yang lain yang disebut CCC. Dengan demikian CCC menjadi alias untuk AAA dan BBB:

namespace AAA
{
   void doSomething() ;
}

namespace BBB
{
   void doSomethingElse() ;
}

namespace CCC
{
   using namespace AAA ;
   using namespace BBB ;
}

void doSomethingAgain()
{
   CCC::doSomething() ;
   CCC::doSomethingElse() ;
}

Anda bahkan dapat mengimpor simbol pilihan dari ruang nama yang berbeda, untuk membangun antarmuka namespace kustom Anda sendiri. Saya belum menemukan penggunaan praktis ini, tetapi secara teori, itu keren.

paercebal
sumber
Bisakah Anda mengklarifikasi, tolong "beri namespace ke semua kode dalam modul Anda"? Apa praktik yang baik untuk merangkum ke modul. Misalnya saya memiliki kelas bilangan kompleks dan fungsi eksternal yang terkait dengan bilangan kompleks. Kelas ini dan kedua fungsi tersebut harus dalam satu namespace?
yanpas
74

Saya tidak melihat menyebutkannya di jawaban lain, jadi inilah 2 sen Kanada saya:

Pada topik "using namespace", pernyataan yang berguna adalah alias namespace, yang memungkinkan Anda untuk "mengganti nama" namespace, biasanya untuk memberikan nama yang lebih pendek. Misalnya, alih-alih:

Some::Impossibly::Annoyingly::Long:Name::For::Namespace::Finally::TheClassName foo;
Some::Impossibly::Annoyingly::Long:Name::For::Namespace::Finally::AnotherClassName bar;

kamu bisa menulis:

namespace Shorter = Some::Impossibly::Annoyingly::Long:Name::For::Namespace::Finally;
Shorter::TheClassName foo;
Shorter::AnotherClassName bar;
Éric Malenfant
sumber
55

Jangan dengarkan setiap orang yang memberi tahu Anda bahwa ruang nama hanyalah ruang nama.

Mereka penting karena mereka dianggap oleh kompiler untuk menerapkan prinsip antarmuka. Pada dasarnya, ini dapat dijelaskan dengan sebuah contoh:

namespace ns {

class A
{
};

void print(A a)
{
}

}

Jika Anda ingin mencetak objek A, kodenya adalah yang ini:

ns::A a;
print(a);

Perhatikan bahwa kami tidak secara eksplisit menyebutkan namespace saat memanggil fungsi. Ini adalah prinsip antarmuka: C ++ menganggap fungsi mengambil tipe sebagai argumen sebagai bagian dari antarmuka untuk tipe itu, jadi tidak perlu menentukan namespace karena parameter sudah menyiratkan namespace.

Sekarang mengapa prinsip ini penting? Bayangkan bahwa penulis kelas A tidak menyediakan fungsi print () untuk kelas ini. Anda harus menyediakannya sendiri. Karena Anda adalah programmer yang baik, Anda akan mendefinisikan fungsi ini di namespace Anda sendiri, atau mungkin di namespace global.

namespace ns {

class A
{
};

}

void print(A a)
{
}

Dan kode Anda dapat mulai memanggil fungsi cetak (a) di mana pun Anda inginkan. Sekarang bayangkan bahwa bertahun-tahun kemudian, penulis memutuskan untuk menyediakan fungsi print (), lebih baik daripada Anda karena dia tahu internal kelasnya dan dapat membuat versi yang lebih baik dari Anda.

Kemudian penulis C ++ memutuskan bahwa versinya dari fungsi print () harus digunakan alih-alih yang disediakan di namespace lain, untuk menghormati prinsip antarmuka. Dan bahwa "peningkatan" fungsi print () ini semudah mungkin, yang berarti Anda tidak perlu mengubah setiap panggilan ke fungsi print (). Itu sebabnya "fungsi antarmuka" (fungsi dalam namespace yang sama dengan kelas) dapat dipanggil tanpa menentukan namespace di C ++.

Dan itulah mengapa Anda harus mempertimbangkan namespace C ++ sebagai "antarmuka" ketika Anda menggunakannya dan mengingat prinsip antarmuka.

Jika Anda ingin penjelasan yang lebih baik tentang perilaku ini, Anda dapat merujuk ke buku Exceptional C ++ dari Herb Sutter

Vincent Robert
sumber
23
Anda benar-benar harus mengubah setiap panggilan untuk mencetak () jika ns :: Cetak ditambahkan, tetapi kompiler akan menandai setiap panggilan sebagai ambigu. Diam-diam beralih ke fungsi baru akan menjadi ide yang mengerikan.
Eclipse
Saya bertanya-tanya sekarang, memiliki apa yang @Vincent katakan bahwa Anda harus mengubah semua panggilan untuk dicetak, jika autor akan menyediakan fungsi ns :: Print (), apa yang ingin Anda katakan? Bahwa ketika penulis menambahkan fungsi ns :: Print (), Anda dapat menghapus implementasi Anda sendiri? Atau Anda hanya akan menambahkan menggunakan ns :: print () using-declaration? Atau menggambar yang lain? Terima kasih
Vaska el gato
36

Proyek C ++ lebih besar yang saya lihat hampir tidak menggunakan lebih dari satu namespace (mis. Meningkatkan perpustakaan).

Sebenarnya boost menggunakan banyak namespace, biasanya setiap bagian boost memiliki namespace sendiri untuk pekerjaan dalam dan kemudian hanya menempatkan antarmuka publik di boost namespace tingkat atas.

Secara pribadi saya berpikir bahwa semakin besar basis kode menjadi, ruang nama yang lebih penting menjadi, bahkan dalam satu aplikasi (atau perpustakaan). Di tempat kerja kami menempatkan setiap modul aplikasi kami di ruang namanya sendiri.

Penggunaan lain (tanpa maksud kata) dari namespaces yang saya gunakan banyak adalah namespace anonim:

namespace {
  const int CONSTANT = 42;
}

Ini pada dasarnya sama dengan:

static const int CONSTANT = 42;

Namun, menggunakan namespace anonim (bukan statis) adalah cara yang disarankan agar kode dan data terlihat hanya dalam unit kompilasi saat ini di C ++.


sumber
13
Kedua contoh Anda setara dengan const int CONSTANT = 42;karena const tingkat atas dalam lingkup namespace sudah menyiratkan hubungan internal. Jadi Anda tidak perlu namespace anonim dalam kasus ini.
sellibitze
19

Perhatikan juga bahwa Anda dapat menambahkan ke namespace. Ini lebih jelas dengan contoh, yang saya maksud adalah bahwa Anda dapat memiliki:

namespace MyNamespace
{
    double square(double x) { return x * x; }
}

dalam file square.h, dan

namespace MyNamespace
{
    double cube(double x) { return x * x * x; }
}

dalam sebuah file cube.h. Ini mendefinisikan ruang nama tunggal MyNamespace(yaitu, Anda dapat menentukan ruang nama tunggal di beberapa file).

Tiram
sumber
11

Di Jawa:

package somepackage;
class SomeClass {}

Dalam C ++:

namespace somenamespace {
    class SomeClass {}
}

Dan menggunakannya, Java:

import somepackage;

Dan C ++:

using namespace somenamespace;

Juga, nama lengkapnya adalah "somepackge.SomeClass" untuk Java dan "somenamespace :: SomeClass" untuk C ++. Menggunakan konvensi tersebut, Anda dapat mengatur seperti dulu di Jawa, termasuk membuat nama folder yang cocok untuk ruang nama. Folder-> paket dan file-> persyaratan kelas tidak ada, jadi Anda dapat memberi nama folder dan kelas secara mandiri dari paket dan ruang nama.

Staale
sumber
6

@ marius

Ya, Anda dapat menggunakan beberapa ruang nama sekaligus, misalnya:

using namespace boost;   
using namespace std;  

shared_ptr<int> p(new int(1));   // shared_ptr belongs to boost   
cout << "cout belongs to std::" << endl;   // cout and endl are in std

[Feb. 2014 - (Apakah sudah begitu lama?): Contoh khusus ini sekarang ambigu, seperti yang ditunjukkan Joey di bawah ini. Boost dan std :: sekarang masing-masing memiliki shared_ptr.]

Adam Hollidge
sumber
2
Perhatikan bahwa sekarang stdjuga sudah shared_ptr, jadi menggunakan keduanya boostdan stdruang nama akan berbenturan ketika Anda mencoba menggunakan a shared_ptr.
Joey
2
Ini adalah contoh yang bagus mengapa banyak perusahaan perangkat lunak akan mencegah impor seluruh ruang nama dengan cara ini. Tidak ada salahnya untuk selalu menentukan namespace, dan jika mereka terlalu lama maka membuat alias atau hanya kelas spesifik yang penting dari namespace.
padi
5

Anda juga dapat memuat "using namespace ..." di dalam fungsi, misalnya:

void test(const std::string& s) {
    using namespace std;
    cout << s;
}
Shadow2531
sumber
3

Secara umum, saya membuat namespace untuk badan kode jika saya percaya mungkin ada konflik fungsi atau ketik nama dengan perpustakaan lain. Ini juga membantu untuk kode merek, ala boost :: .

Adam Hollidge
sumber
3

Saya lebih suka menggunakan namespace tingkat atas untuk aplikasi dan sub ruang nama untuk komponen.

Cara Anda dapat menggunakan kelas dari ruang nama lain sangat mirip dengan cara di java. Anda dapat menggunakan "use NAMESPACE" yang mirip dengan pernyataan "import PACKAGE", mis. Use std. Atau Anda menentukan paket sebagai awalan dari kelas yang dipisahkan dengan "::", misalnya std :: string. Ini mirip dengan "java.lang.String" di Jawa.

tuan
sumber
3

Perhatikan bahwa namespace di C ++ benar-benar hanya ruang nama. Mereka tidak menyediakan enkapsulasi yang dilakukan oleh paket di Java, jadi Anda mungkin tidak akan menggunakannya terlalu banyak.

Kristopher Johnson
sumber
2

Saya telah menggunakan ruang nama C ++ dengan cara yang sama saya lakukan di C #, Perl, dll. Ini hanya pemisahan semantik simbol antara hal-hal perpustakaan standar, hal-hal pihak ketiga, dan kode saya sendiri. Saya akan menempatkan aplikasi saya sendiri di satu namespace, kemudian komponen pustaka yang dapat digunakan kembali di namespace lain untuk pemisahan.

spoulson
sumber
2

Perbedaan lain antara java dan C ++, adalah bahwa dalam C ++, hirarki namespace tidak perlu mengatur tata letak sistem file. Jadi saya cenderung menempatkan seluruh pustaka yang dapat digunakan kembali dalam satu namespace, dan subsistem dalam pustaka dalam subdirektori:

#include "lib/module1.h"
#include "lib/module2.h"

lib::class1 *v = new lib::class1();

Saya hanya akan meletakkan subsistem dalam ruang nama bersarang jika ada kemungkinan konflik nama.

KeithB
sumber