Apakah mengandalkan konversi argumen implisit dianggap berbahaya?

10

C ++ memiliki fitur (saya tidak tahu nama yang tepat), yang secara otomatis memanggil konstruktor yang cocok dari tipe parameter jika tipe argumen bukan yang diharapkan.

Contoh yang sangat mendasar dari hal ini adalah memanggil fungsi yang mengharapkan a std::stringdengan const char*argumen. Kompiler akan secara otomatis menghasilkan kode untuk memanggil std::stringkonstruktor yang sesuai .

Saya bertanya-tanya, apakah buruk untuk keterbacaan seperti yang saya kira?

Ini sebuah contoh:

class Texture {
public:
    Texture(const std::string& imageFile);
};

class Renderer {
public:
    void Draw(const Texture& texture);
};

Renderer renderer;
std::string path = "foo.png";
renderer.Draw(path);

Apakah itu baik-baik saja? Atau apakah itu terlalu jauh? Jika saya tidak melakukannya, bisakah saya membuat Dentang atau GCC memperingatkan tentang hal itu?

futlib
sumber
1
bagaimana jika Draw dibebani dengan versi string nanti?
ratchet freak
1
per jawaban @Dave Rager, saya tidak berpikir ini akan dikompilasi pada semua kompiler. Lihat komentar saya pada jawabannya. Tampaknya, menurut standar c ++, Anda tidak dapat mengaitkan konversi tersirat seperti ini. Anda hanya dapat melakukan satu konversi dan tidak lebih.
Jonathan Henson
OK maaf, sebenarnya tidak mengkompilasi ini. Memperbarui contoh dan itu masih mengerikan, IMO.
futlib

Jawaban:

24

Ini disebut sebagai konstruktor pengonversi (atau terkadang konstruktor implisit atau konversi implisit).

Saya tidak mengetahui pergantian waktu kompilasi untuk memperingatkan ketika ini terjadi, tetapi sangat mudah untuk dicegah; cukup gunakan explicitkata kunci.

class Texture {
public:
    explicit Texture(const std::string& imageFile);
};

Seperti apakah mengkonversi konstruktor adalah ide yang baik: Itu tergantung.

Keadaan di mana konversi implisit masuk akal:

  • Kelas ini cukup murah untuk dibangun sehingga Anda tidak peduli jika dibangun secara implisit.
  • Beberapa kelas secara konseptual mirip dengan argumen mereka (seperti std::stringmerefleksikan konsep yang sama dengan yang const char *secara implisit dapat dikonversi dari), sehingga konversi implisit masuk akal.
  • Beberapa kelas menjadi jauh lebih tidak menyenangkan untuk digunakan jika konversi implisit dinonaktifkan. (Pikirkan harus secara eksplisit memanggil std :: string setiap kali Anda ingin melewatkan string literal. Bagian dari Boost serupa.)

Keadaan di mana konversi implisit kurang masuk akal:

  • Konstruksi mahal (seperti contoh Tekstur Anda, yang memerlukan pemuatan dan penguraian file grafik).
  • Kelas secara konseptual sangat berbeda dengan argumen mereka. Pertimbangkan, misalnya, wadah mirip array yang menggunakan ukurannya sebagai argumen:
    FlagList kelas
    {
        FlagList (int initial_size); 
    };

    membatalkan SetFlags (const FlagList & flag_list);

    int main () {
        // Sekarang kompilasi ini, meskipun sama sekali tidak jelas
        // apa yang dilakukannya.
        SetFlags (42);
    }
  • Konstruksi mungkin memiliki efek samping yang tidak diinginkan. Sebagai contoh, sebuah AnsiStringkelas tidak boleh membangun secara implisit dari a UnicodeString, karena konversi Unicode-ke-ANSI dapat kehilangan informasi.

Bacaan lebih lanjut:

Josh Kelley
sumber
3

Ini lebih dari komentar daripada jawaban tetapi terlalu besar untuk dimasukkan ke dalam komentar.

Menariknya, g++jangan biarkan saya melakukan itu:

#include <iostream>
#include <string>

class Texture {
        public:
                Texture(const std::string& imageFile)
                {
                        std::cout << "Texture()" << std::endl;
                }
};

class Renderer {
        public:
                void Draw(const Texture& texture)
                {
                        std::cout << "Renderer.Draw()" << std::endl;
                }
};

int main(int argc, char* argv[])
{
        Renderer renderer;
        renderer.Draw("foo.png");

        return 0;
}

Menghasilkan yang berikut:

$ g++ -o Conversion.exe Conversion.cpp 
Conversion.cpp: In function int main(int, char**)’:
Conversion.cpp:23:25: error: no matching function for call to Renderer::Draw(const char [8])’
Conversion.cpp:14:8: note: candidate is: void Renderer::Draw(const Texture&)

Namun, jika saya mengubah jalur ke:

   renderer.Draw(std::string("foo.png"));

Itu akan melakukan konversi itu.

Dave Rager
sumber
Itu memang "fitur" yang menarik di g ++. Saya kira itu adalah salah satu bug yang hanya memeriksa satu jenis dalam daripada turun secara rekursif sejauh mungkin pada waktu kompilasi untuk menghasilkan kode yang benar, atau ada bendera yang perlu diatur dalam perintah g ++ Anda.
Jonathan Henson
1
en.cppreference.com/w/cpp/language/implicit_cast tampaknya g ++ mengikuti standar dengan sangat ketat. Ini adalah kompiler Microsoft atau Mac yang menjadi agak terlalu murah hati dengan kode OP. Terutama mengatakan adalah pernyataan: "Ketika mempertimbangkan argumen untuk konstruktor atau ke fungsi konversi yang ditentukan pengguna, hanya satu urutan konversi standar yang diizinkan (jika tidak, konversi yang ditentukan pengguna dapat dirantai secara efektif)."
Jonathan Henson
Ya, saya baru saja melemparkan kode bersama untuk menguji beberapa gccopsi kompiler (yang sepertinya tidak ada untuk mengatasi kasus khusus ini). Saya tidak melihat lebih jauh ke dalamnya (saya seharusnya bekerja :-) tetapi mengingat gcckepatuhan terhadap standar dan penggunaan explicitkata kunci, opsi kompiler mungkin dianggap tidak perlu.
Dave Rager
Konversi implisit tidak dirantai, dan Texturemungkin seharusnya tidak dibangun secara implisit (sesuai dengan pedoman dalam jawaban lain), jadi situs panggilan yang lebih baik adalah renderer.Draw(Texture("foo.png"));(dengan asumsi itu berfungsi seperti yang saya harapkan).
Blaisorblade
3

Ini disebut konversi tipe implisit. Secara umum itu adalah hal yang baik, karena menghambat pengulangan yang tidak perlu. Misalnya, Anda secara otomatis mendapatkan std::stringversi Drawtanpa harus menulis kode tambahan apa pun untuknya. Ini juga dapat membantu dalam mengikuti prinsip buka-tutup, karena memungkinkan Anda memperluas Rendererkemampuan tanpa memodifikasi Renderersendiri.

Di sisi lain, ini bukan tanpa kekurangan. Ini bisa menyulitkan untuk mencari tahu dari mana argumen berasal, untuk satu hal. Kadang-kadang dapat menghasilkan hasil yang tidak terduga dalam kasus lain. Untuk itulah explicitkata kunci tersebut. Jika Anda meletakkannya di Texturekonstruktor itu dinonaktifkan menggunakan konstruktor itu untuk konversi tipe implisit. Saya tidak mengetahui metode untuk memperingatkan global tentang konversi tipe implisit, tetapi itu tidak berarti metode tidak ada, hanya bahwa gcc memiliki sejumlah besar opsi yang tidak dapat dipahami.

Karl Bielefeldt
sumber