Class template

Container generik buatan sendiri dan metode template.

Daftar isi

Topik ini membahas class template, yaitu kelas yang tipe anggotanya ditentukan oleh parameter template. Pembahasan mencakup pendefinisian container generik sederhana, instansiasinya menjadi kelas konkret, serta metode template yang menerima tipe berbeda dari tipe kelasnya. Contoh yang dipakai adalah Kantong, sebuah container buatan sendiri yang penyimpanannya dititipkan ke std::vector.

Definisi class template#

Sebuah class template dideklarasikan dengan daftar parameter template di depan class atau struct. Pada contoh di bawah, T menjadi tipe elemen yang disimpan Kantong, sedangkan seluruh method kelas ditulis terhadap T seolah-olah tipenya sudah konkret. Anggota datanya berupa std::vector<T>, sehingga kapasitas penampungnya menyesuaikan diri tanpa pengelolaan memori manual.

cpp kantong.cpp
#include <iostream>
#include <string>
#include <vector>

template <typename T>
class Kantong {
public:
    void tambah(const T& nilai) {
        data_.push_back(nilai);
    }

    std::size_t ukuran() const {
        return data_.size();
    }

    const T& ambil(std::size_t posisi) const {
        return data_.at(posisi);
    }

    template <typename U>
    void salinDari(const Kantong<U>& lain) {
        for (std::size_t i = 0; i < lain.ukuran(); ++i) {
            tambah(static_cast<T>(lain.ambil(i)));
        }
    }

private:
    std::vector<T> data_;
};

int main() {
    Kantong<int> angka;
    angka.tambah(10);
    angka.tambah(24);
    angka.tambah(7);

    std::cout << "Jumlah elemen   : " << angka.ukuran() << "\n";
    std::cout << "Elemen pertama  : " << angka.ambil(0) << "\n";

    Kantong<double> pecahan;
    pecahan.salinDari(angka);
    std::cout << "Salinan pecahan : " << pecahan.ambil(0) << " dan " << pecahan.ambil(1) << "\n";

    Kantong<std::string> kata;
    kata.tambah("pagi");
    kata.tambah("sore");
    std::cout << "Kata terakhir   : " << kata.ambil(kata.ukuran() - 1) << "\n";
    return 0;
}
text Keluaran
Jumlah elemen   : 3
Elemen pertama  : 10
Salinan pecahan : 10 dan 24
Kata terakhir   : sore

Deklarasi Kantong<int> menghasilkan kelas konkret dengan T bertipe int, sedangkan Kantong<double> dan Kantong<std::string> menghasilkan dua kelas lain yang berdiri sendiri. Ketiganya adalah tipe yang berbeda, dan semuanya bersumber dari definisi yang sama. Method tambah, ukuran, dan ambil dipanggil dengan tipe masing-masing kelas, dan method ukuran mengembalikan std::size_t, yaitu tipe bilangan bulat tak bertanda yang dipakai pustaka standar untuk jumlah elemen.

Satu keunggulan mekanisme ini sering luput diperhatikan, yaitu method class template yang tidak pernah dipanggil tidak diinstansiasi. Kelas Kantong memuat method salinDari, tetapi program di atas hanya menginstansiasi salinDari<int> pada Kantong<double> dan tidak pada Kantong<std::string>. Syarat pada T pun diperiksa per method. Karena itu, kelas tetap dapat dipakai untuk tipe yang tidak mendukung seluruh operasinya, selama method yang menyebut operasi tersebut tidak pernah dipanggil.

Metode template di dalam kelas#

Method salinDari menyertakan parameter tipe sendiri U di dalam kelas yang sudah bertemplate, sehingga method semacam itu disebut metode template. Parameter U memungkinkan penyalinan dari Kantong<int> ke Kantong<double> tanpa kedua kelas itu saling mengenal satu sama lain. Setiap pemanggilan menginstansiasi method untuk pasangan T dan U yang terpakai, misalnya salinDari<int> pada Kantong<double>.

Perbedaannya dengan method biasa pada class template perlu diperjelas. Method tambah dan ambil adalah anggota keluarga Kantong<T>, sehingga tipenya ditentukan oleh T milik kelasnya. salinDari berbeda karena membawa parameter tipe sendiri, sehingga dapat menerima Kantong<U> dengan U apa pun yang dapat dikonversikan ke T. Kombinasi kedua bentuk ini lazim pada container karena kelas bertanggung jawab atas elemennya sendiri, sedangkan hubungan antar kelas yang berbeda tipe diselesaikan oleh method template.

Konversi static_cast<T> di dalam method itu menjadi bagian penting dari kontraknya. Apabila elemen U tidak dapat dikonversi ke T, error muncul saat instantiation dengan pesan yang menunjuk baris konversinya. Desain semacam ini wajar untuk container karena penyalinan antartipe kompatibel adalah kebutuhan yang sering muncul, sedangkan konversi yang tidak bermakna tetap ditolak compiler.

Kaitannya dengan container pustaka standar#

std::vector, std::string, dan std::array pada pustaka standar adalah class template dengan pola yang sama persis, yaitu perilaku yang ditulis sekali dan tipe elemen yang diisi penggunanya. Selisihnya, pustaka standar menambahkan alokator, iterator, dan jaminan kompleksitas yang lengkap. Container Kantong di topik ini sengaja dibuat ramping supaya bentuk dasar class template terlihat tanpa gangguan.

Pemakaian bertingkat mengikuti pola yang sama tanpa aturan tambahan. Deklarasi Kantong<Kantong<int>> menghasilkan container yang elemennya adalah container lain, sebagaimana std::vector<std::vector<int>> pada data dua dimensi. Tipe yang diisi ke parameter template boleh berupa tipe apa pun yang sudah lengkap, termasuk instans class template lain, dan seluruh syarat operasinya diperiksa oleh instantiation yang sama.

Untuk kode sehari-hari, container pustaka standar tetap menjadi pilihan utama karena sudah teruji dan lengkap. Class template buatan sendiri lazim dipakai ketika kebutuhannya spesifik, misalnya struktur data khusus untuk jadwal piket atau arsip cadangan berkas, dan definisinya mengikuti pola yang sama dengan Kantong. Prinsip yang berlaku pun sama, yaitu tipe elemen wajib mendukung operasi yang dipanggil oleh method kelas, dan error akibat operasi yang hilang baru muncul saat instantiation.

Kesalahan umum#

Tiga kekeliruan berikut sering muncul ketika class template baru mulai dipakai.

  • Menganggap Kantong<int> dan Kantong<double> dapat saling diisi. Keduanya adalah dua tipe yang berbeda, sehingga penugasan langsung antar keduanya ditolak compiler. Penyalinan isinya dilakukan melalui metode template seperti salinDari atau lewat perulangan eksplisit.
  • Memakai tipe elemen yang tidak mendukung operasi method kelas. Error baru muncul saat instantiation dengan pesan yang menunjuk baris di dalam method, bukan pada baris pemanggilan. Pastikan tipe elemen menyediakan constructor penyalinan dan operator yang diperlukan.
  • Memisahkan definisi class template ke berkas .cpp dan hanya menyisakan deklarasinya di header. Compiler yang menginstansiasi dari berkas lain tidak melihat definisinya, sehingga linker melaporkan undefined reference. Definisi class template ditempatkan pada header secara utuh.

Ringkasan#

  • Class template mendefinisikan kelas dengan parameter tipe, dan setiap instansiasi seperti Kantong<int> menghasilkan kelas konkret yang berdiri sendiri.
  • Metode template di dalam kelas menerima parameter tipe tambahan, sehingga operasi lintas instans seperti penyalinan antartipe dapat ditulis sekali saja.
  • Container pustaka standar dibangun dengan pola yang sama, dan tipe elemen wajib menyediakan operasi yang dipakai method kelasnya.

Referensi#