Sejauh ini setiap pointer kita bertipe: int* menunjuk int, double* menunjuk double, dan compiler menolak silang tipe di antara mereka. Ada satu pengecualian yang disengaja bahasa: void*, pointer yang menunjuk “tipe apa pun”. Ia bisa menampung alamat int, double, array, atau objek lain tanpa keluhan compiler — tetapi harga kebebasan itu nyata: void* tidak tahu tipe sasarannya, sehingga tidak bisa didereference dan tidak bisa dihitung. Chapter ini membahas cara memakainya dengan aman, use case klasiknya di API C, dan alasan mengapa kode C++ modern berusaha menghindarinya.

Pointer Tanpa Tipe Sasaran #

Aturan void* bisa diringkas dalam dua kalimat: ia menerima alamat dari tipe pointer mana pun tanpa cast, dan untuk memakai isinya ia harus dikembalikan ke tipe asalnya dengan cast eksplisit. Compiler membantu di sisi pertama (implisit, gratis), dan menolak di sisi kedua bila kita malas:

cpp
#include <cstring>
#include <iostream>

int main() {
    int angka = 250;
    void* v = &angka;  // void* bisa menampung alamat tipe apa pun

    // std::cout << *v;  // ERROR: void* tidak tahu tipe sasarannya

    int* p = static_cast<int*>(v);  // cast kembali ke tipe asal
    std::cout << "*p : " << *p << "\n";

    // Use case klasik: std::memcpy dari header <cstring> bekerja pada void*
    int sumber[4] = {3, 6, 9, 12};
    int salinan[4] = {0, 0, 0, 0};
    std::memcpy(salinan, sumber, sizeof(sumber));

    std::cout << "salinan: ";
    bool pertama = true;
    for (int x : salinan) {
        if (!pertama) std::cout << " ";
        std::cout << x;
        pertama = false;
    }
    std::cout << "\n";

    return 0;
}

Output yang Dihasilkan:

text
*p : 250
salinan: 3 6 9 12

Bagian-bagian pentingnya:

  1. Baris 7 (void* v = &angka;): konversi dari int* ke void* terjadi secara implisit dan tanpa risiko — setiap alamat sah untuk ditampung void*.
  2. Baris 9 (baris komentar): dereference langsung ditolak compiler. Coba hilangkan komentarnya, dan g++ 14.2 menjawab:
  3. Baris 11 (static_cast<int*>(v)): static_cast yang kita kenal pada Chapter operator cast mengembalikan void* ke int*. Setelah itu *p berperilaku normal. Ketentuan pentingnya: cast balik harus ke tipe yang benar — void* tidak menyimpan jejak tipe asalnya, dan salah tipe menghasilkan undefined behavior.
  4. Baris 14-15 (std::memcpy): fungsi penyalin blok byte dari header <cstring> — warisan API C — menerima parameter void* untuk sumber dan tujuan agar bisa dipakai pada tipe apa pun. Argumen ketiga sizeof(sumber) memberitahu berapa byte yang disalin; itulah mengapa void* selalu berpasangan dengan ukuran.
  5. Baris 18-25: hasil salinan dicek dengan range-based-for: keempat nilai sumber pindah utuh ke salinan.
text Pesan galat bila dereference void* langsung
error: 'void*' is not a pointer-to-object type

Pesan tersebut muncul karena “tipe apa pun” juga berarti “tanpa tipe”: compiler tidak tahu berapa byte yang harus dibaca dan bagaimana menafsirkannya. Demi alasan yang sama, pointer arithmetic pada void* juga tidak terdefinisi — tidak ada ukuran elemen untuk dikalikan.

Use Case: API C dan Fungsi Generik Lawas #

void* adalah kunci interoperabilitas dengan dunia C, yang belum mengenal template. Fungsi standar yang parameter atau kembaliannya bertipe void* di antaranya std::memcpy dan std::memmove (penyalin memori), std::malloc (alokator C yang mengembalikan void*), serta std::qsort — fungsi sortir generik yang menerima pointer ke elemen pertama, jumlah elemen, ukuran elemen, dan function pointer pembanding, semuanya tanpa tahu tipe datanya. Pola desainnya konsisten: data dikirim sebagai void* + ukuran dalam byte, dan konversi tipe dipercayakan penuh kepada pemanggil.

Kapan Menghindari void* #

Dalam kode C++ murni, void* hampir selalu bisa digantikan mekanisme yang lebih aman:

  • Fungsi generik → template (Section Generic Programming dan Templates), yang memeriksa tipe saat kompilasi tanpa cast manual.
  • Menampung “nilai tipe apa pun” → std::variant, std::any, atau std::optional pada Section Standard Library, yang menjaga informasi tipenya.
  • Meneruskan array → std::span atau std::vector, yang membawa ukuran sehingga tidak mudah salah hitung byte.

Dengan pengganti-pengganti itu, peran void* menyempit ke batas sistem: API C warisan dan pengelolaan memori tingkat rendah. Di tempat-tempat itulah kita tetap menemuinya, dan sekarang kita sudah tahu cara memperlakukannya dengan benar: cast eksplisit ke tipe yang benar, ukuran yang akurat, dan tidak pernah dereference mentah.

Pointer generik selesai. Satu kategori terakhir yang wajib kita kuasai sebelum menutup mekanik pointer: pointer yang menunjuk objek yang sudah tidak ada — dangling pointer, salah satu sumber bug paling licik di C++.

Referensi: cppreference — cv qualifier (void*) , cppreference — std::memcpy , cppreference — std::qsort .