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:
#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:
*p : 250
salinan: 3 6 9 12Bagian-bagian pentingnya:
- Baris 7 (
void* v = &angka;): konversi dariint*kevoid*terjadi secara implisit dan tanpa risiko — setiap alamat sah untuk ditampungvoid*. - Baris 9 (baris komentar): dereference langsung ditolak compiler. Coba hilangkan komentarnya, dan g++ 14.2 menjawab:
- Baris 11 (
static_cast<int*>(v)):static_castyang kita kenal pada Chapter operator cast mengembalikanvoid*keint*. Setelah itu*pberperilaku normal. Ketentuan pentingnya: cast balik harus ke tipe yang benar —void*tidak menyimpan jejak tipe asalnya, dan salah tipe menghasilkan undefined behavior. - Baris 14-15 (
std::memcpy): fungsi penyalin blok byte dari header<cstring>— warisan API C — menerima parametervoid*untuk sumber dan tujuan agar bisa dipakai pada tipe apa pun. Argumen ketigasizeof(sumber)memberitahu berapa byte yang disalin; itulah mengapavoid*selalu berpasangan dengan ukuran. - Baris 18-25: hasil salinan dicek dengan range-based-for: keempat nilai
sumberpindah utuh kesalinan.
error: 'void*' is not a pointer-to-object typePesan 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, ataustd::optionalpada Section Standard Library, yang menjaga informasi tipenya. - Meneruskan array →
std::spanataustd::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 .