Topik ini membahas perpindahan nilai antartipe, mulai dari konversi implisit yang berjalan diam-diam sampai empat bentuk cast bernama C++. Materinya mencakup mengapa konversi otomatis bisa menghasilkan nilai yang berbeda dari harapan, cara menyatakan konversi secara eksplisit, dan pemilihan cast yang tepat untuk tiap kebutuhan. Seluruh contoh berupa program satu berkas yang dapat dijalankan di C++17.
Konversi implisit dan perangkapnya#
Konversi implisit dilakukan compiler tanpa ditulis secara terang-terangan, yaitu ketika sebuah nilai dipakai dalam konteks yang menuntut tipe berbeda. Compiler mengizinkannya selama aturannya terdefinisi, tetapi hasilnya bisa mengejutkan karena perhitungan asli terjadi lebih dahulu sebelum nilai dikonversi.
#include <iostream>
int main() {
int jumlah = 7;
int pembagi = 2;
double hasilPembagian = jumlah / pembagi; // pembagian int terjadi lebih dahulu
int dipangkas = 3.9; // bagian pecahan dibuang tanpa dibulatkan
std::cout << "hasil pembagian = " << hasilPembagian << "\n";
std::cout << "nilai dipangkas = " << dipangkas << "\n";
return 0;
}hasil pembagian = 3
nilai dipangkas = 3Dua perangkap terlihat dari keluaran tersebut. Pertama, jumlah / pembagi dihitung sebagai pembagian bilangan bulat sehingga menghasilkan 3, dan hasil itulah yang kemudian dikonversi menjadi 3.0. Kedua, inisialisasi int dipangkas = 3.9 membuang bagian pecahan tanpa membulatkan, sehingga nilainya menjadi 3 dan bukan 4. Konversi seperti ini disebut penyempitan (narrowing) karena informasi bisa hilang, dan compiler tidak menolaknya pada pengaturan bawaan.
Promosi dan perbandingan tipe campuran#
Ketika satu ekspresi memadukan beberapa tipe bilangan, compiler menyamakannya melalui aturan usual arithmetic conversions. Tipe kecil seperti char dan bool dipromosikan menjadi int, yaitu dinaikkan secara otomatis ke tipe yang lebih luas. Pasangan campuran int dengan double disamakan ke double. Program berikut memeragakan promosi tersebut beserta satu perangkap perbandingannya.
#include <iostream>
int main() {
char huruf = 'A';
bool selesai = true;
int selisih = -1;
unsigned int batas = 1;
std::cout << "huruf + 1 = " << huruf + 1 << "\n";
std::cout << "selesai + 10 = " << selesai + 10 << "\n";
std::cout << "1 / 2 = " << 1 / 2 << "\n";
std::cout << "1 / 2.0 = " << 1 / 2.0 << "\n";
std::cout << "selisih < batas = " << (selisih < batas) << "\n";
return 0;
}huruf + 1 = 66
selesai + 10 = 11
1 / 2 = 0
1 / 2.0 = 0.5
selisih < batas = 0Ekspresi huruf + 1 menghasilkan 66, yaitu nilai numerik karakter, karena char dipromosikan menjadi int sebelum dijumlahkan. Pada perbandingan selisih < batas, kedua tipe disamakan ke unsigned int sehingga -1 berubah menjadi 4294967295, dan hasilnya false yang tercetak 0. Nilai matematika -1 jelas lebih kecil daripada 1, tetapi hasil perbandingannya berlawanan dengan hitungan tersebut. Perbaikannya menjaga kedua sisi bertipe sama, misalnya menyimpan batas sebagai int, atau mengonversi secara eksplisit dengan static_cast<int>(batas).
Menyalakan peringatan konversi dengan -Wconversion#
Karena konversi implisit diperbolehkan, compiler hanya melaporkannya bila diminta. Opsi -Wconversion menandai setiap konversi yang berpotensi mengubah nilai, sehingga penyempitan diam-diam pada bagian pertama dapat ditemukan sejak kompilasi. Berkas uji berikut memuat dua konversi yang menyempitkan.
# berkas dengan dua konversi yang menyempitkan nilai
cat > /tmp/kecilkan.cpp << 'EOF'
int main() {
int dipangkas = 3.9;
char inisial = 300;
return 0;
}
EOF
# kompilasi dengan peringatan konversi diaktifkan
g++ -Wconversion /tmp/kecilkan.cpp -o /tmp/kecilkan/tmp/kecilkan.cpp: In function ‘int main()’:
/tmp/kecilkan.cpp:2:21: warning: conversion from ‘double’ to ‘int’ changes value from ‘3.8999999999999999e+0’ to ‘3’ [-Wfloat-conversion]
2 | int dipangkas = 3.9;
| ^~~
/tmp/kecilkan.cpp:3:20: warning: overflow in conversion from ‘int’ to ‘char’ changes value from ‘300’ to ‘44’ [-Woverflow]
3 | char inisial = 300;
| ^~~Kedua penugasan tetap menghasilkan program karena keduanya terdefinisi, tetapi peringatan menunjukkan nilai yang benar-benar tersimpan. Angka 300 menjadi 44 karena hanya bit rendah yang dimuat oleh char, dan perubahan semacam itu nyaris mustahil dilacak tanpa bantuan compiler. Opsi ini tergolong teliti sehingga biasanya dihidupkan pada tahap pengujian, sedangkan -Wall menjadi bawaan sehari-hari.
static_cast: konversi yang dinyatakan secara eksplisit#
static_cast<tipe>(nilai) menyatakan konversi yang sudah pasti benar saat kompilasi. Bentuk ini membuat niat konversi terlihat di dalam kode, dan compiler tetap memeriksa apakah kedua tipe memang dapat dihubungkan.
#include <iostream>
int main() {
int total = 17;
int banyak = 4;
double rataRata = static_cast<double>(total) / banyak;
char huruf = static_cast<char>(65);
std::cout << "rata-rata = " << rataRata << "\n";
std::cout << "huruf = " << huruf << "\n";
return 0;
}rata-rata = 4.25
huruf = AEkspresi static_cast<double>(total) / banyak memaksa total dikonversi terlebih dahulu, sehingga pembagiannya menjadi pembagian pecahan dan menghasilkan 4.25. Pada baris kedua, bilangan 65 dikonversi menjadi char yang tercetak sebagai huruf A sesuai tabel karakter. Pola pertama inilah yang dipakai setiap kali rata-rata dihitung dari dua bilangan bulat.
dynamic_cast, const_cast, dan reinterpret_cast#
Tiga cast bernama lainnya melayani kebutuhan yang tidak bisa ditangani static_cast. Program berikut memakai dua kelas sederhana untuk mendemonstrasikan dynamic_cast, sedangkan pengertian kelas itu sendiri tidak perlu dikuasai sekarang karena yang diperiksa adalah perilaku castnya.
#include <iostream>
class Hewan {
public:
virtual ~Hewan() = default;
};
class Kucing : public Hewan {
public:
void mengeong() const { std::cout << "Meong!\n"; }
};
int main() {
Kucing kucing{};
Hewan hewanBiasa{};
// static_cast: konversi yang sudah pasti benar saat kompilasi
double rataRata = static_cast<double>(7) / 2;
std::cout << "static_cast : " << rataRata << "\n";
// dynamic_cast: konversi turun yang diperiksa saat program berjalan
Hewan* penunjukKucing = &kucing;
Hewan* penunjukHewan = &hewanBiasa;
Kucing* k1 = dynamic_cast<Kucing*>(penunjukKucing);
Kucing* k2 = dynamic_cast<Kucing*>(penunjukHewan);
std::cout << "dynamic_cast : " << (k1 != nullptr) << " dan " << (k2 == nullptr) << "\n";
k1->mengeong();
// const_cast: melepas sifat const dari sebuah pointer
const int batas = 90;
const int* penunjukBatas = &batas;
int* penunjukBebas = const_cast<int*>(penunjukBatas);
std::cout << "const_cast : " << *penunjukBebas << "\n";
// reinterpret_cast: menafsirkan ulang pola bit
int kode = 65;
char* bytePertama = reinterpret_cast<char*>(&kode);
std::cout << "reinterpret_cast : " << *bytePertama << "\n";
return 0;
}static_cast : 3.5
dynamic_cast : 1 dan 1
Meong!
const_cast : 90
reinterpret_cast : Adynamic_cast melakukan konversi turun pada hierarki kelas dan memeriksa kebenarannya saat program berjalan. Pemanggilan pertama berhasil karena objeknya memang Kucing, sedangkan pemanggilan kedua menghasilkan nullptr karena Hewan tidak berupa Kucing. Nilai nullptr itulah yang harus diperiksa sebelum pointer dipakai. Syarat pemeriksaan ini adalah kelas dasar yang punya setidaknya satu fungsi virtual, pada contoh ini berupa destructor virtualnya.
const_cast melepas sifat const dari sebuah pointer sehingga menghasilkan bentuk non-const yang dapat diakses, sebagaimana keluaran 90 pada contoh. reinterpret_cast menafsirkan ulang pola bit tanpa mengubah isinya, sehingga byte pertama dari int 65 dibaca sebagai karakter A pada platform yang menyusun byte dari nilai kecil.
Kapan setiap bentuk cast dipakai#
Keempat cast bernama memiliki wilayah yang tidak saling menimpa, sehingga pemilihannya dapat diringkas dalam satu tabel.
| Cast | Kegunaan | Diperiksa pada |
|---|---|---|
static_cast | Konversi tipe biasa yang diketahui benar, misalnya int ke double | Saat kompilasi |
dynamic_cast | Konversi turun pada hierarki kelas berfungsi virtual | Saat program berjalan |
const_cast | Melepas atau menambah kualifikasi const | Saat kompilasi |
reinterpret_cast | Menafsirkan ulang pola bit antartipe pointer | Tidak diperiksa |
Bentuk cast gaya C seperti (double)total juga sah secara sintaks, tetapi bentuknya buram karena satu tulisan dapat berarti beberapa jenis konversi sekaligus. Pada C++, cast bernama dipilih karena menyatakan jenis operasinya dan mudah dicari dalam kode.
Kesalahan umum#
Tiga kekeliruan berikut paling sering muncul saat konversi tipe ditulis sendiri.
- Mengira konversi
doublekeintmembulatkan. Baik konversi implisit maupunstatic_cast<int>(3.9)menghasilkan 3 karena bagian pecahan dibuang, dan pembulatan yang benar menggunakanstd::lround(3.9)dari header<cmath>yang menghasilkan 4. - Memanggil
dynamic_castpada kelas dasar tanpa fungsi virtual. Compiler menolaknya dengan pesan yang menyebutsource type is not polymorphic, dan perbaikannya memastikan kelas dasar memiliki setidaknya satu fungsi virtual. - Menyebar cast ke beberapa baris hanya untuk membungkam pesan compiler. Penyebab konversi tidak dianalisis, sehingga nilai yang salah berpindah dari peringatan menjadi keluaran program. Perbaikannya menelusuri tipe yang bertabrakan lalu memperbaiki deklarasinya.
Ringkasan#
- Konversi implisit berjalan otomatis, tetapi perhitungan asli terjadi lebih dahulu, sehingga
7 / 2menghasilkan 3 dan3.9yang dimasukkan keintmenjadi 3. static_cast<tipe>(nilai)menyatakan konversi secara eksplisit dan diperiksa compiler, dan bentuk inilah pilihan utama untuk konversi tipe biasa.dynamic_castmengonversi turun pada hierarki kelas dan menghasilkannullptrapabila objeknya tidak cocok, sedangkanconst_castmengatur kualifikasiconstdanreinterpret_castmenafsirkan ulang pola bit.- Penulisan nilai lewat hasil
const_castpada objek yang dideklarasikanconstmerupakan undefined behavior.
Referensi#
- Implicit conversions — cppreference — Daftar konversi implisit yang dilakukan compiler beserta urutannya.
- Usual arithmetic conversions — cppreference — Aturan penyamaan tipe dalam ekspresi aritmetika campuran.
- Warning Options — GCC
— Daftar opsi peringatan compiler GNU, termasuk
-Wconversion. - Explicit type conversion — cppreference — Definisi cast bernama dan cast gaya C dalam standar.
- dynamic_cast conversion — cppreference — Aturan konversi turun yang diperiksa saat program berjalan.
- const_cast conversion — cppreference — Dokumentasi pelepasan kualifikasi const.
- reinterpret_cast conversion — cppreference — Aturan penafsiran ulang pola bit antartipe.
- Explicit type conversion (casting) and static_cast — LearnCpp — Panduan bertahap konversi eksplisit dan static_cast.