Bagikan melalui


Konversi Tipe yang Ditentukan Pengguna (C++)

Konversi menghasilkan nilai baru dari beberapa jenis dari nilai dengan jenis yang berbeda. Konversi standar dibangun ke dalam bahasa C++ dan mendukung jenis bawaannya, dan Anda dapat membuat konversi yang ditentukan pengguna untuk melakukan konversi ke, dari, atau di antara jenis yang ditentukan pengguna.

Konversi standar melakukan konversi antara jenis bawaan, antara penunjuk atau referensi ke jenis yang terkait dengan pewarisan, ke dan dari pointer yang batal, dan ke penunjuk null. Untuk informasi selengkapnya, lihat Konversi Standar. Konversi yang ditentukan pengguna melakukan konversi antara jenis yang ditentukan pengguna, atau antara jenis yang ditentukan pengguna dan jenis bawaan. Anda dapat menerapkannya sebagai konstruktor Konversi atau sebagai fungsi Konversi.

Konversi dapat menjadi eksplisit—ketika programmer memanggil satu jenis untuk dikonversi ke jenis lain, seperti dalam pemeran atau inisialisasi langsung—atau implisit—ketika bahasa atau program memanggil jenis yang berbeda dari yang diberikan oleh programmer.

Konversi implisit dicoba ketika:

  • Argumen yang diberikan ke fungsi tidak memiliki jenis yang sama dengan parameter yang cocok.

  • Nilai yang dikembalikan dari fungsi tidak memiliki jenis yang sama dengan jenis pengembalian fungsi.

  • Ekspresi penginisialisasi tidak memiliki jenis yang sama dengan objek yang diinisialisasi.

  • Ekspresi yang mengontrol pernyataan kondisional, konstruksi perulangan, atau sakelar tidak memiliki jenis hasil yang diperlukan untuk mengontrolnya.

  • Operand yang disediakan ke operator tidak memiliki jenis yang sama dengan parameter operand yang cocok. Untuk operator bawaan, kedua operan harus memiliki jenis yang sama, dan dikonversi ke jenis umum yang dapat mewakili keduanya. Untuk informasi selengkapnya, lihat Konversi Standar. Untuk operator yang ditentukan pengguna, setiap operand harus memiliki jenis yang sama dengan parameter operand yang cocok.

Ketika satu konversi standar tidak dapat menyelesaikan konversi implisit, pengkompilasi dapat menggunakan konversi yang ditentukan pengguna, diikuti secara opsional oleh konversi standar tambahan, untuk menyelesaikannya.

Ketika dua atau beberapa konversi yang ditentukan pengguna yang melakukan konversi yang sama tersedia di situs konversi, konversi dikatakan ambigu. Ambiguitas tersebut adalah kesalahan karena pengkompilasi tidak dapat menentukan salah satu konversi yang tersedia yang harus dipilihnya. Namun, ini bukan kesalahan hanya untuk menentukan beberapa cara untuk melakukan konversi yang sama karena kumpulan konversi yang tersedia dapat berbeda di lokasi yang berbeda dalam kode sumber—misalnya, tergantung pada file header mana yang disertakan dalam file sumber. Selama hanya satu konversi yang tersedia di situs konversi, tidak ada ambiguitas. Ada beberapa cara agar konversi ambigu dapat muncul, tetapi yang paling umum adalah:

  • Beberapa warisan. Konversi didefinisikan dalam lebih dari satu kelas dasar.

  • Panggilan fungsi ambigu. Konversi didefinisikan sebagai konstruktor konversi dari jenis target dan sebagai fungsi konversi dari jenis sumber. Untuk informasi selengkapnya, lihat Fungsi konversi.

Anda biasanya dapat menyelesaikan ambiguitas hanya dengan memenuhi syarat nama jenis yang terlibat lebih lengkap atau dengan melakukan cast eksplisit untuk mengklarifikasi niat Anda.

Baik konstruktor konversi maupun fungsi konversi mematuhi aturan kontrol akses anggota, tetapi aksesibilitas konversi hanya dipertimbangkan jika dan kapan konversi yang tidak ambigu dapat ditentukan. Ini berarti bahwa konversi dapat ambigu meskipun tingkat akses konversi yang bersaing akan mencegahnya digunakan. Untuk informasi selengkapnya tentang aksesibilitas anggota, lihat Kontrol Akses Anggota.

Kata kunci eksplisit dan masalah dengan konversi implisit

Secara default saat Anda membuat konversi yang ditentukan pengguna, pengkompilasi dapat menggunakannya untuk melakukan konversi implisit. Terkadang ini yang Anda inginkan, tetapi di lain waktu aturan sederhana yang memandu pengompilasi dalam membuat konversi implisit dapat menyebabkannya menerima kode yang tidak Anda inginkan.

Salah satu contoh terkenal dari konversi implisit yang dapat menyebabkan masalah adalah konversi ke bool. Ada banyak alasan bahwa Anda mungkin ingin membuat jenis kelas yang dapat digunakan dalam konteks Boolean—misalnya, sehingga dapat digunakan untuk mengontrol if pernyataan atau perulangan—tetapi ketika pengompilasi melakukan konversi yang ditentukan pengguna ke jenis bawaan, pengompilasi diizinkan untuk menerapkan konversi standar tambahan setelahnya. Niat dari konversi standar tambahan ini adalah untuk memungkinkan hal-hal seperti promosi dari short ke int, tetapi juga membuka pintu untuk konversi yang kurang jelas—misalnya, dari bool ke int, yang memungkinkan jenis kelas Anda digunakan dalam konteks bilangan bulat yang tidak pernah Anda maksudkan. Masalah khusus ini dikenal sebagai Masalah Bool Aman. Masalah semacam ini adalah di mana explicit kata kunci dapat membantu.

Kata explicit kunci memberi tahu pengkompilasi bahwa konversi yang ditentukan tidak dapat digunakan untuk melakukan konversi implisit. Jika Anda menginginkan kenyamanan sinaks dari konversi implisit sebelum explicit kata kunci diperkenalkan, Anda harus menerima konsekuensi yang tidak diinginkan yang kadang-kadang dibuat atau menggunakan fungsi konversi bernama yang kurang nyaman sebagai solusinya. Sekarang, dengan menggunakan explicit kata kunci, Anda dapat membuat konversi nyaman yang hanya dapat digunakan untuk melakukan pemeran eksplisit atau inisialisasi langsung, dan itu tidak akan menyebabkan jenis masalah yang dicontohkan oleh Masalah Bool Aman.

Kata explicit kunci dapat diterapkan ke konstruktor konversi sejak C++98, dan ke fungsi konversi sejak C++11. Bagian berikut berisi informasi selengkapnya tentang cara menggunakan explicit kata kunci.

Konstruktor konversi

Konstruktor konversi menentukan konversi dari jenis yang ditentukan pengguna atau bawaan ke jenis yang ditentukan pengguna. Contoh berikut menunjukkan konstruktor konversi yang mengonversi dari jenis double bawaan ke jenis Moneyyang ditentukan pengguna .

#include <iostream>

class Money
{
public:
    Money() : amount{ 0.0 } {};
    Money(double _amount) : amount{ _amount } {};

    double amount;
};

void display_balance(const Money balance)
{
    std::cout << "The balance is: " << balance.amount << std::endl;
}

int main(int argc, char* argv[])
{
    Money payable{ 79.99 };

    display_balance(payable);
    display_balance(49.95);
    display_balance(9.99f);

    return 0;
}

Perhatikan bahwa panggilan pertama ke fungsi display_balance, yang mengambil argumen jenis Money, tidak memerlukan konversi karena argumennya adalah jenis yang benar. Namun, pada panggilan kedua ke display_balance, konversi diperlukan karena jenis argumen, double dengan nilai 49.95, bukan yang diharapkan fungsi. Fungsi tidak dapat menggunakan nilai ini secara langsung, tetapi karena ada konversi dari jenis argumen—doubleke jenis parameter yang cocok—Money—nilai sementara dari jenis Money dibuat dari argumen dan digunakan untuk menyelesaikan panggilan fungsi. Dalam panggilan ketiga ke display_balance, perhatikan bahwa argumen bukan double, tetapi sebaliknya dengan float nilai 9.99—namun panggilan fungsi masih dapat diselesaikan karena pengkompilasi dapat melakukan konversi standar—dalam hal ini, dari float ke double—dan kemudian melakukan konversi yang ditentukan pengguna dari double ke Money untuk menyelesaikan konversi yang diperlukan.

Mendeklarasikan konstruktor konversi

Aturan berikut berlaku untuk mendeklarasikan konstruktor konversi:

  • Jenis target konversi adalah jenis yang ditentukan pengguna yang sedang dibangun.

  • Konstruktor konversi biasanya mengambil tepat satu argumen, yang merupakan jenis sumber. Namun, konstruktor konversi dapat menentukan parameter tambahan jika setiap parameter tambahan memiliki nilai default. Jenis sumber tetap menjadi jenis parameter pertama.

  • Konstruktor konversi, seperti semua konstruktor, tidak menentukan jenis pengembalian. Menentukan jenis pengembalian dalam deklarasi adalah kesalahan.

  • Konstruktor konversi bisa eksplisit.

Konstruktor konversi eksplisit

Dengan mendeklarasikan konstruktor konversi menjadi explicit, konstruktor hanya dapat digunakan untuk melakukan inisialisasi langsung objek atau untuk melakukan cast eksplisit. Ini mencegah fungsi yang menerima argumen jenis kelas juga secara implisit menerima argumen jenis sumber konstruktor konversi, dan mencegah jenis kelas diinisialisasi dari nilai jenis sumber. Contoh berikut menunjukkan cara menentukan konstruktor konversi eksplisit, dan efek yang dimilikinya pada kode apa yang terbentuk dengan baik.

#include <iostream>

class Money
{
public:
    Money() : amount{ 0.0 } {};
    explicit Money(double _amount) : amount{ _amount } {};

    double amount;
};

void display_balance(const Money balance)
{
    std::cout << "The balance is: " << balance.amount << std::endl;
}

int main(int argc, char* argv[])
{
    Money payable{ 79.99 };        // Legal: direct initialization is explicit.

    display_balance(payable);      // Legal: no conversion required
    display_balance(49.95);        // Error: no suitable conversion exists to convert from double to Money.
    display_balance((Money)9.99f); // Legal: explicit cast to Money

    return 0;
}

Dalam contoh ini, perhatikan bahwa Anda masih dapat menggunakan konstruktor konversi eksplisit untuk melakukan inisialisasi langsung .payable Jika sebaliknya Anda menyalin-menginisialisasi Money payable = 79.99;, itu akan menjadi kesalahan. Panggilan pertama ke display_balance tidak terpengaruh karena argumen adalah jenis yang benar. Panggilan kedua ke display_balance adalah kesalahan, karena konstruktor konversi tidak dapat digunakan untuk melakukan konversi implisit. Panggilan ketiga ke display_balance legal karena cast eksplisit ke Money, tetapi perhatikan bahwa kompilator masih membantu menyelesaikan pemeran dengan memasukkan cast implisit dari float ke double.

Meskipun kenyamanan memungkinkan konversi implisit dapat menggoda, melakukannya dapat memperkenalkan bug yang sulit ditemukan. Aturan praktisnya adalah membuat semua konstruktor konversi eksplisit kecuali ketika Anda yakin bahwa Anda ingin konversi tertentu terjadi secara implisit.

Fungsi konversi

Fungsi konversi menentukan konversi dari jenis yang ditentukan pengguna ke jenis lain. Fungsi-fungsi ini kadang-kadang disebut sebagai "operator transmisi" karena, bersama dengan konstruktor konversi, dipanggil ketika nilai dilemparkan ke jenis yang berbeda. Contoh berikut menunjukkan fungsi konversi yang mengonversi dari jenis yang ditentukan pengguna, Money, ke jenis bawaan, double:

#include <iostream>

class Money
{
public:
    Money() : amount{ 0.0 } {};
    Money(double _amount) : amount{ _amount } {};

    operator double() const { return amount; }
private:
    double amount;
};

void display_balance(const Money balance)
{
    std::cout << "The balance is: " << balance << std::endl;
}

Perhatikan bahwa variabel amount anggota dibuat privat dan bahwa fungsi konversi publik ke jenis double diperkenalkan hanya untuk mengembalikan nilai amount. Dalam fungsi display_balance, konversi implisit terjadi ketika nilai balance dialirkan ke output standar dengan menggunakan operator <<penyisipan aliran . Karena tidak ada operator penyisipan aliran yang didefinisikan untuk jenis Moneyyang ditentukan pengguna , tetapi ada satu untuk jenis doublebawaan , pengkompilasi dapat menggunakan fungsi konversi dari Money ke untuk double memenuhi operator penyisipan aliran.

Fungsi konversi diwariskan oleh kelas turunan. Fungsi konversi dalam kelas turunan hanya mengambil alih fungsi konversi yang diwariskan saat dikonversi ke jenis yang sama persis. Misalnya, fungsi konversi yang ditentukan pengguna dari int operator kelas turunan tidak mengambil alih—atau bahkan memengaruhi—fungsi konversi yang ditentukan pengguna dari operator kelas dasar singkat, meskipun konversi standar menentukan hubungan konversi antara int dan short.

Mendeklarasikan fungsi konversi

Aturan berikut berlaku untuk mendeklarasikan fungsi konversi:

  • Jenis target konversi harus dideklarasikan sebelum deklarasi fungsi konversi. Kelas, struktur, enumerasi, dan typedef tidak dapat dideklarasikan dalam deklarasi fungsi konversi.

    operator struct String { char string_storage; }() // illegal
    
  • Fungsi konversi tidak mengambil argumen. Menentukan parameter apa pun dalam deklarasi adalah kesalahan.

  • Fungsi konversi memiliki jenis pengembalian yang ditentukan oleh nama fungsi konversi, yang juga merupakan nama jenis target konversi. Menentukan jenis pengembalian dalam deklarasi adalah kesalahan.

  • Fungsi konversi bisa virtual.

  • Fungsi konversi bisa eksplisit.

Fungsi konversi eksplisit

Ketika fungsi konversi dinyatakan eksplisit, fungsi tersebut hanya dapat digunakan untuk melakukan cast eksplisit. Ini mencegah fungsi yang menerima argumen jenis target fungsi konversi juga secara implisit menerima argumen jenis kelas, dan mencegah instans jenis target diinisialisasi dari nilai jenis kelas. Contoh berikut menunjukkan cara menentukan fungsi konversi eksplisit dan efeknya pada kode apa yang terbentuk dengan baik.

#include <iostream>

class Money
{
public:
    Money() : amount{ 0.0 } {};
    Money(double _amount) : amount{ _amount } {};

    explicit operator double() const { return amount; }
private:
    double amount;
};

void display_balance(const Money balance)
{
    std::cout << "The balance is: " << (double)balance << std::endl;
}

Di sini operator fungsi konversi ganda telah dibuat eksplisit, dan cast eksplisit ke jenis double telah diperkenalkan dalam fungsi display_balance untuk melakukan konversi. Jika transmisi ini dihilangkan, pengkompilasi tidak akan dapat menemukan operator << penyisipan aliran yang sesuai untuk jenis Money dan kesalahan akan terjadi.