Fare affidamento sulla conversione implicita di argomenti è considerato pericoloso?


10

C ++ ha una funzione (non riesco a capirne il nome proprio), che chiama automaticamente costruttori corrispondenti di tipi di parametri se i tipi di argomento non sono quelli previsti.

Un esempio molto semplice di ciò è chiamare una funzione che prevede un std::stringcon un const char*argomento. Il compilatore genererà automaticamente il codice per invocare il std::stringcostruttore appropriato .

Mi chiedo, è male per la leggibilità come penso che sia?

Ecco un esempio:

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);

Va bene? O va troppo lontano? Se non dovessi farlo, posso in qualche modo far avvisare Clang o GCC?


1
cosa succede se Draw è stato sovraccaricato con una versione di stringa in un secondo momento?
maniaco del cricchetto,

1
per la risposta di @Dave Rager, non credo che questo verrà compilato su tutti i compilatori. Vedi il mio commento sulla sua risposta. Apparentemente, secondo lo standard c ++, non è possibile concatenare conversioni implicite come questa. Puoi fare solo una conversione e non più.
Jonathan Henson,

OK scusa, non ho compilato questo. Aggiornato l'esempio ed è ancora orribile, IMO.
futlib

Risposte:


24

Questo è indicato come costruttore di conversione (o talvolta costruttore implicito o conversione implicita).

Non sono a conoscenza di un passaggio di compilazione per avvisare quando ciò si verifica, ma è molto facile da prevenire; basta usare la explicitparola chiave.

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

Per sapere se convertire i costruttori è una buona idea: dipende.

Circostanze in cui la conversione implicita ha senso:

  • La classe è abbastanza economica da costruire che non ti importa se è implicitamente costruita.
  • Alcune classi sono concettualmente simili ai loro argomenti (come std::stringriflettere lo stesso concetto dal const char *quale può implicitamente convertirsi), quindi la conversione implicita ha senso.
  • Alcune classi diventano molto più spiacevoli da usare se la conversione implicita è disabilitata. (Pensa di dover invocare esplicitamente std :: string ogni volta che vuoi passare letteralmente una stringa. Parti di Boost sono simili.)

Circostanze in cui la conversione implicita ha meno senso:

  • La costruzione è costosa (come l'esempio di Texture, che richiede il caricamento e l'analisi di un file grafico).
  • Le lezioni sono concettualmente molto diverse dalle loro argomentazioni. Considera, ad esempio, un contenitore simile a un array che assume le sue dimensioni come argomento:
    class FlagList
    {
        FlagList (int initial_size); 
    };

    void SetFlags (const FlagList & flag_list);

    int main () {
        // Ora questo viene compilato, anche se non è affatto ovvio
        // cosa sta facendo.
        SetFlags (42);
    }
  • La costruzione potrebbe avere effetti collaterali indesiderati. Ad esempio, una AnsiStringclasse non dovrebbe essere implicitamente costruita da a UnicodeString, poiché la conversione da Unicode a ANSI potrebbe perdere informazioni.

Ulteriori letture:


3

Questo è più un commento che una risposta ma troppo grande per inserire un commento.

È interessante notare g++che non mi permette di farlo:

#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;
}

Produce quanto segue:

$ 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&)

Tuttavia, se cambio la riga in:

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

Eseguirà quella conversione.


Questa è davvero una "caratteristica" interessante in g ++. Suppongo che sia un bug che controlla solo un tipo in profondità invece di scendere ricorsivamente il più possibile al momento della compilazione per generare il codice corretto, oppure c'è un flag che deve essere impostato nel comando g ++.
Jonathan Henson,

1
en.cppreference.com/w/cpp/language/implicit_cast sembra che g ++ stia seguendo lo standard abbastanza rigorosamente. È il compilatore di Microsoft o Mac ad essere un po 'troppo generoso con il codice dell'OP. In particolare si dice che la frase: "Se si considera l'argomento di un costruttore o di una funzione di conversione definita dall'utente, è consentita solo una sequenza di conversione standard (altrimenti le conversioni definite dall'utente potrebbero essere concatenate in modo efficace)".
Jonathan Henson

Sì, ho appena messo insieme il codice per testare alcune delle gccopzioni del compilatore (che non sembra che ce ne siano per affrontare questo caso particolare). Non ho studiato molto più a fondo (dovrei funzionare :-), ma data gccl'adesione allo standard e l'uso della explicitparola chiave, un'opzione del compilatore è stata probabilmente considerata non necessaria.
Dave Rager,

Le conversioni implicite non sono concatenate e Textureprobabilmente non dovrebbero essere costruite implicitamente (secondo le linee guida in altre risposte), quindi un sito di chiamata migliore sarebbe renderer.Draw(Texture("foo.png"));(supponendo che funzioni come mi aspetto).
Blaisorblade,

3

Si chiama conversione di tipo implicito. In generale è una buona cosa, in quanto inibisce inutili ripetizioni. Ad esempio, ottieni automaticamente una std::stringversione di Drawsenza dover scrivere alcun codice aggiuntivo per esso. Può anche aiutare a seguire il principio aperto-chiuso, in quanto consente di estendere Rendererle capacità senza modificarsi Renderer.

D'altra parte, non è privo di inconvenienti. Può essere difficile capire da dove provenga una discussione, per prima cosa. A volte può produrre risultati imprevisti in altri casi. Ecco a cosa explicitserve la parola chiave. Se lo metti sul Texturecostruttore disabilita l'uso di quel costruttore per la conversione di tipo implicita. Non sono a conoscenza di un metodo per mettere in guardia a livello globale sulla conversione di tipo implicita, ma ciò non significa che un metodo non esista, solo che gcc ha un numero incomprensibilmente grande di opzioni.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.