Namespace

Mengelompokkan pengenal dengan namespace, penggunaan using, dan organisasi kode sebelum proyek banyak berkas.

Daftar isi

Topik ini membahas namespace sebagai alat pengelompokan pengenal, yaitu nama variabel, fungsi, dan tipe yang dibuat sendiri. Materinya mencakup cara mendeklarasikan namespace, penggunaan deklarasi using dan arahan using, namespace bersarang dengan sintaks C++17, serta kebiasaan yang disarankan sebelum kode tumbuh menjadi proyek banyak berkas. Seluruhnya diperagakan dengan program satu berkas yang dapat dijalankan.

Mengelompokkan pengenal dengan namespace#

Namespace memberi wilayah nama tersendiri bagi sekumpulan pengenal, sehingga dua pengenal bernama sama tidak bertabrakan selama keduanya berada di namespace berbeda. Akses ke isinya ditulis dengan operator lingkup ::, misalnya gudang::tampilkan(). Program berikut menempatkan fungsi dan variabel di dalam namespace, lengkap dengan dua pengenal bernama sama yang hidup berdampingan.

cpp
#include <iostream>

namespace gudang {
    int jumlahBarang = 120;

    void tampilkan() {
        std::cout << "Jumlah barang di gudang: " << jumlahBarang << "\n";
    }
}

namespace gudangCitra {
    int jumlahBarang = 45;
}

int main() {
    gudang::tampilkan();
    std::cout << "Jumlah barang milik Citra: " << gudangCitra::jumlahBarang << "\n";
    return 0;
}
text Keluaran
Jumlah barang di gudang: 120
Jumlah barang milik Citra: 45

Dua variabel bernama jumlahBarang berdampingan tanpa konflik karena masing-masing milik namespace berbeda, dan nama yang dimaksud ditentukan oleh awalannya. Di dalam tubuh fungsi gudang::tampilkan, pemakaian jumlahBarang tanpa awalan tetap merujuk ke milik gudang karena nama dicari pada namespace terdekat terlebih dahulu. Pola inilah yang dipakai pustaka standar, yaitu menempatkan semua pengenalnya di namespace std, sehingga std::cout berarti cout milik pustaka standar.

Membuka kembali namespace yang sama#

Namespace bersifat terbuka, yaitu menuliskan blok namespace dengan nama yang sama lebih dari sekali tidak membuat pengelompokan baru, melainkan melanjutkan yang sudah ada. Seluruh anggota dari setiap blok berakhir di satu namespace yang sama dan dapat dipanggil dengan awalan yang sama pula. Sifat ini bukan kebetulan pada sintaks, melainkan fondasi pemisahan deklarasi dan implementasi pada proyek banyak berkas.

cpp
#include <iostream>

namespace gudang {
    int jumlahBarang = 120;
}

namespace gudang {
    void laporkan() {
        std::cout << "gudang::jumlahBarang = " << jumlahBarang << "\n";
    }
}

int main() {
    gudang::laporkan();
    return 0;
}
text Keluaran
gudang::jumlahBarang = 120

Blok kedua membuka gudang lalu menambahkan fungsi laporkan ke dalamnya, sehingga pemanggilan gudang::laporkan() di dalam main ditemukan tanpa hambatan. Pada proyek nyata, deklarasi fungsi ditulis di dalam namespace pada berkas header, sedangkan definisinya dibuka kembali pada berkas sumbernya. Urutan blok tidak menentukan makna apa pun, sehingga anggota yang ditulis belakangan tetap berada pada wilayah yang sama.

Deklarasi using dan arahan using#

Menuliskan awalan lengkap setiap kali tidak selalu nyaman, dan di sinilah dua bentuk using berperan. Deklarasi using memangkas satu nama saja, sedangkan arahan using namespace memangkas seluruh isi sebuah namespace sekaligus. Keduanya diperagakan berikut ini pada satu nama saja.

cpp
#include <iostream>

namespace pengukuran {
    const double PI = 3.14159;

    double luasLingkaran(double jariJari) {
        return PI * jariJari * jariJari;
    }
}

int main() {
    using pengukuran::luasLingkaran;  // hanya nama luasLingkaran yang dipangkas

    std::cout << "luas lingkaran r = 7 : " << luasLingkaran(7.0) << "\n";
    return 0;
}
text Keluaran
luas lingkaran r = 7 : 153.938

Deklarasi using pengukuran::luasLingkaran; hanya memperkenalkan satu nama, sehingga risikonya kecil dan sumber tabrakan tetap terkendali. Arahan using namespace pengukuran; akan memperkenalkan seluruh isinya, termasuk PI, dan bentuk inilah yang dipakai banyak contoh buku lewat using namespace std;. Arahan semacam itu praktis untuk program latihan yang pendek, tetapi ia memperluas wilayah pencarian nama secara besar dan membuka peluang tabrakan ketika jumlah baris bertambah.

Alias namespace#

Alias namespace memberi nama lain yang lebih pendek untuk namespace yang sudah ada, ditulis dengan bentuk namespace alias = nama;. Alias bukan salinan, sehingga alat::KALIBRASI dan peralatanUkuranPresisi::KALIBRASI merujuk ke pengenal yang persis sama. Alias juga ditetapkan saat kompilasi dan berlaku pada lingkup tempat ia ditulis.

cpp
#include <iostream>

namespace peralatanUkuranPresisi {
    const double KALIBRASI = 0.02;
}

namespace alat = peralatanUkuranPresisi;

int main() {
    std::cout << "kalibrasi = " << alat::KALIBRASI << "\n";
    return 0;
}
text Keluaran
kalibrasi = 0.02

Kegunaannya paling terasa pada namespace bersarang yang panjang, misalnya aplikasi::laporan::detail yang dapat dipangkas menjadi satu kata pendek di dalam satu berkas. Pustaka standar mengikuti pola yang sama, misalnya alias namespace fs = std::filesystem; yang lazim dijumpai pada kode yang bekerja dengan berkas. Berbeda dengan deklarasi using yang memangkas satu nama saja, alias tetap mempertahankan awalan pencarian nama pada seluruh anggota yang diakses melaluinya.

Namespace bersarang dengan sintaks C++17#

Namespace dapat disusun bertingkat untuk memisahkan lapisan kode, misalnya utilitas di dalam aplikasi. Sebelum C++17, penulisannya memerlukan blok namespace di dalam blok namespace lain, sedangkan C++17 menyediakan penulisan ringkas dengan operator :: langsung pada deklarasi namespace.

cpp
#include <iostream>
#include <string>

namespace aplikasi {
namespace utilitas {
    int panjangTeks(const std::string& teks) {
        return static_cast<int>(teks.length());
    }
}
}

// C++17 menyediakan penulisan namespace bersarang dengan operator ::
namespace aplikasi::laporan {
    void cetakRingkas() {
        std::cout << "Panjang nama: " << aplikasi::utilitas::panjangTeks("Citra") << "\n";
    }
}

int main() {
    aplikasi::laporan::cetakRingkas();
    return 0;
}
text Keluaran
Panjang nama: 5

Kedua gaya penulisan menghasilkan struktur yang sama, yaitu namespace utilitas dan laporan di dalam namespace aplikasi. Bentuk ringkas namespace aplikasi::laporan { } mengurangi takik bertingkat dan membuat hierarki terbaca sekilas. Akses ke pengenal yang dalam tetap ditulis penuh, misalnya aplikasi::utilitas::panjangTeks, atau dipangkas dengan deklarasi using bila nama itu dipakai berkali-kali.

Nama global dan operator :: tunggal#

Seluruh pengenal yang ditulis di luar namespace apa pun berada pada namespace global, dan operator :: yang ditulis tanpa awalan merujuk langsung ke sana. Bentuk ini berguna ketika sebuah nama lokal menutupi nama global yang sama di dalam fungsi. Program berikut mendeklarasikan dua variabel kapasitas pada lingkup berbeda lalu mengakses keduanya.

cpp
#include <iostream>

int kapasitas = 500;

int main() {
    int kapasitas = 20;

    std::cout << "kapasitas lokal : " << kapasitas << "\n";
    std::cout << "kapasitas global: " << ::kapasitas << "\n";
    return 0;
}
text Keluaran
kapasitas lokal : 20
kapasitas global: 500

Nama kapasitas tanpa awalan dicari pada lingkup terdekat terlebih dahulu, sehingga yang ditemukan adalah versi lokal dengan nilai 20. Awalan :: kosong memaksa pencarian dimulai dari namespace global, sehingga versi 500 ditemukan pula. Mekanisme ini juga menjelaskan bentuk std::cout yang selama ini ditulis, yaitu nama std dicari pada namespace global lalu anggotanya diakses dengan operator lingkup yang sama.

Kebiasaan yang disarankan#

Organisasi namespace menentukan kenyamanan proyek yang sedang tumbuh, dan beberapa kebiasaan berikut menjaga kode tetap mudah dikelola.

  • Beri setiap modul kode satu namespace bernama jelas, misalnya pengukuran untuk kode perhitungan, sehingga asal setiap pengenal terbaca dari awalannya.
  • Utamakan penulisan awalan lengkap seperti std::cout pada kode produksi, karena asal nama langsung terlihat dan tidak ada risiko tabrakan.
  • Tempatkan deklarasi using pada lingkup sekecil mungkin, misalnya di dalam satu fungsi, agar pemangkasan namanya tidak menyebar ke seluruh berkas.
  • Hindari arahan using namespace di dalam berkas header, karena setiap berkas yang menyertakannya ikut menyerap seluruh isi namespace tersebut.

Satu tambahan yang perlu dikenal sejak dini adalah namespace tanpa nama, yaitu namespace { ... } tanpa pengenal. Isinya hanya terlihat dari berkas tempat ia ditulis, sehingga cocok untuk helper yang tidak dimaksudkan dipakai berkas lain. Dengan kebiasaan di atas, perpindahan ke proyek banyak berkas hanya menambah langkah build, tanpa mengubah cara nama dikelola.

Ringkasan#

  • Namespace mengelompokkan pengenal dan diakses dengan operator ::, sehingga nama yang sama di namespace berbeda tidak bertabrakan.
  • Deklarasi using memangkas satu nama saja, sedangkan arahan using namespace memangkas seluruh isi namespace dan berisiko memperlebar wilayah tabrakan.
  • C++17 menyediakan penulisan namespace bersarang berbentuk namespace a::b { } yang setara dengan blok bersarang klasik.
  • Arahan using namespace di berkas header dihindari, dan namespace tanpa nama menyembunyikan pengenal dari berkas lain.

Referensi#