User:Fresbeeplayer/Development/Tutorials/Common Programming Mistakes (it)

    From KDE TechBase


    Development/Tutorials/Common Programming Mistakes


    Errori di Programmazione Comuni
    Tutorial Series   Getting Started
    Previous   None
    What's Next   n/a
    Further Reading   APIs to avoid

    Prefazione

    Questo tutorial mira a combinare le esperienze degli sviluppatori KDE su cosa fare e cosa non fare in merito alle librerie Qt e KDE. Oltre agli errori, vengono coperte anche cose che non sono necessariamente "bachi" ma che rendono il codice più lento e di difficile lettura.

    C++ in generale

    Questa sezione ti guida attraverso alcuni degli angoli più remoti del C++ che tendono ad essere mal utilizzati o dei quali la gente si sbaglia.

    Namespace anonimi contro static

    <! -- controllare il passaggio file-static If you have a method in a class that does not access any members and therefore does not need an object to operate, make it static. If additionally it is a private helper function that is not needed outside of the file, make it a file-static function. That hides the symbol completely. -->

    Se hai un metodo in una classe che non accede ad alcun membro e quindi non ha bisogno di un oggetto per funzionare, rendilo statico. Se in più è una funzione di supporto privata che non viene utilizzata all'esterno del file, rendila file-static. In questo modo essa viene nascosta completamente.

    <! -- un namespace anonimo non ha linkage interno? Symbols defined in a C++ anonymous namespace do not have internal linkage. Anonymous namespaces only give a unique name for that translation unit and that is it; they do not change the linkage of the symbol at all. Linkage is not changed on those because the second phase of two-phase name lookup ignores functions with internal linkages. Also, entities with internal linkage cannot be used as template arguments. -->

    Le entità definite in un namespace anonimo in C++ non hanno linkage interni. I namespace anonimi offrono soltanto un nome unico per quella translation unit e basta; non cambiano in nessun modo il linkage dell'identificatore. Il linkage non viene cambiato perché la seconda delle due fasi di ricerca dei nomi ignora le funzioni con linkage interno. Per di più, le entità con linkage interno non possono essere usate come argomento di un template.

    A questo punto invece di usare namespace anonimi usa la parola chiave static se non vuoi che un simbolo venga esportato.

    Problemi di puntatore Nullo

    Prima di tutto: va bene eliminare un puntatore nullo. Quindi costruttori come il seguente che controllano che il valore sia nullo prima di eliminarlo sono semplicemente ridondanti:

    if ( ptr ) {

      delete ptr;
    

    }

    Da notare comunque, che un controllo per valore nullo è richiesto quando cancelli un array - questo perché altrimenti un compilatore relativamente recente per Solaris non lo gestisce opportunamente.

    Quando elimini un puntatore, assicurati anche di settarlo a 0 in modo che futuri tentativi di cancellazione non falliscano in una doppia eliminazione. Per cui il modo completo e corretto di procedere è:

    delete ptr; ptr = 0;

    Potresti notare che i puntatori nulli sono variamente indicati in uno di questi tre modi: 0, 0L e NULL. In C, NULL è definito come un puntatore nullo di tipo void. Ma in C++ ciò non è possibile a causa di un controllo di tipo più stretto. Perciò, moderne implementazioni del C++ lo rendono come un "magico" puntatore nullo costante il quale può essere assegnato a qualunque altro puntatore. D'altra parte le implementazioni più vecchie di C++ semplicemente lo associano a 0 o 0L, il quale non tiene conto di alcuna sicurezza di tipo - si potrebbe assegnarlo ad una variabile intera, ovviamente sbagliando.

    Nel contesto dei puntatori, la costante intera zero significa "puntatore nullo" - irrispettoso della rappresentazione binaria di un puntatore nullo. Ciò significa che la scelta tra 0, 0L e NULL è una questione di stile personale e abitudine piuttosto che tecnica - fintantoché nel codice SVN di KDE vedrai 0 usato più comunemente di NULL.

    Notare, comunque, che se vuoi passare un puntatore nullo costante ad una funzione nella lista delle variabili degli argomenti, *devi* esplicitamente farne il cast in un puntatore - il compilatore assume di default il contesto degli interi, il quale può o non può coincidere con la rappresentazione binaria di un puntatore. Di nuovo, non ha importanza il fatto che fai il cast a 0, 0L o NULL, ma la rappresentazione più corta è generalmente preferita.

    Variabili membro

    Incontrerai quattro maggiori stili per segnare le variabili membre delle classi in KDE:

    • m_variabile m minuscola, underscore ed il nome della variabile che comincia con una lettera minuscola. Questo è lo stile più comune ed uno dei preferiti nel codice delle kdelibs.
    • mVariabile m minuscola ed il nome della variabile che comincia con una lettera maiuscola.
    • variabile_ il nome della variabile comincia con la lettera minuscola ed alla fine un underscore.
    • _variabile un underscore e poi il nome della variabile con la lettera iniziale minuscola. Questa notazione di solito è sconsigliata siccome è anche utilizzata in qualche codice per i parametri delle funzioni.

    Come accade spesso non c'è un modo corretto per farlo, perciò ricorda sempre di rispettare la sintassi utilizzata dall'applicazione/libreria alla quale stai facendo commit.

    Variabili statiche

    Cerca di limitare il numero di variabili statiche nel tuo codice, specialmente quando fate il commit per una libreria. Costruzione ed inizializzazione di un grande numero di variabili statiche fa veramente male ai tempi di avvio.

    <! -- ricontrollare Do not use class-static variables, especially not in libraries and loadable modules though it is even discouraged in applications. Static objects lead to lots of problems such as hard to debug crashes due to undefined order of construction/destruction. -->

    Non usare variabili class-static, in particolare non nelle librerie e nei moduli sebbene sia anche scoraggiato nelle applicazioni. Oggetti statici portano ad un sacco di problemi tra cui difficoltà di debug dei crash dovuto ad un ordine indefinito di costruttore/distruttore.

    Invece, usa un puntatore statico insieme a K_GLOBAL_STATIC definito in kglobal.h ed usato in questo modo:

    class A { ... };

    K_GLOBAL_STATIC(A, globaleA)

    void faQualcosa() {

        A *a = globaleA;
        ...
    

    }

    void faQualcosAltro() {

       if (globaleA.isDestroyed()) {
           return;
       }
       A *a = globaleA;
       ...
    

    }

    void installaPostRoutine() {

       qAddPostRoutine(globaleA.destroy);
    

    }

    Vedi la documentazione delle API per più informazioni su K_GLOBAL_STATIC.

    Dati costanti

    Se hai bisogno di qualche dato costante per semplici tipi di dato in molti punti, fai bene a definirli una volta sola in un posto centrale, onde evitare errori di digitazione in una delle istanze. Se i dati cambiano hai bisogno di editare solo in un punto.

    Anche se usati una sola volta è meglio definirli da un'altra parte, per evitare inspiegabili "numeri magici" nel codice (cmp. 42). Di solito ciò viene fatto in cima al file per non doverli ricercare.

    Definisci i dati costanti usando i costrutti del C++, non le istruzioni del preprocessore, come potresti essere abituato a fare dal C. In questo modo il compilatore può aiutarti a trovare errori facendo il controllo di tipo.

    // Corretto! static const int LaRispostaATutteLeDomande = 42; // Sbagliato!

    1. define LaRispostaATutteLeDomande 42

    <! -- ricontrollare If defining a constant array do not use a pointer as data type. Instead use the data type and append the array symbol with undefined length, [], behind the name. Otherwise you also define a variable to some const data. That variable could mistakenly be assigned a new pointer to, without the compiler complaining about. And accessing the array would have one indirection, because first the value of the variable needs to be read. -->

    Se stai definendo un array costante non usare un puntatore come tipo di dato. Invece usa il suo tipo ed appendi il simbolo dell'array di indefinita lunghezza, [], dopo il nome. Altrimenti definirai anche una variabile con qualche dato costante. La variabile potrebbe per sbaglio essere assegnata ad un altro puntatore, senza che il compilatore se ne lamenti. E l'accesso all'array sarebbe indiretto, perché per primo deve essere letto il valore della variabile.

    // Corretto! static const char UnaStringa[] = "Esempio"; // Sbagliato! static const char* UnaStringa = "Esempio"; // Sbagliato!

    1. define UnaStringa "Esempio"

    Dichiarazioni anticipate

    Ridurrai i tempi di compilazione dichiarando anticipatamente le classi quando possibile invece di includere i rispettivi headers. Per esempio:

    1. include <QWidget> // lento
    2. include <QStringList> // lento
    3. include <QString> // lento

    class QualcheInterfaccia { public:

       virtual void azioneWidget( QWidget *widget ) =0;
       virtual void azioneStringa( const QString& str ) =0;
       virtual void azioniListaStringhe( const QStringList& strLista ) =0;
    

    };

    Dovrebbe invece essere scritto in questo modo:

    class QWidget; // veloce class QStringList; // veloce class QString; // veloce class QualcheInterfaccia { public:

       virtual void azioneWidget( QWidget *widget ) =0;
       virtual void azioneStringa( const QString& str ) =0;
       virtual void azioniListaStringhe( const QStringList& strLista ) =0;
    

    };

    Iteratori

    Preferire iteratori costanti e conservare end()

    Preferisci l'uso dei const_iterators rispetto ai normali iteratori quando possibile. I containers, implicitamente condivisi, spesso eseguono un detach() (vedi [1] per più informazioni, n.d.t.) quando viene fatta una chiamata ad un metodo begin() o end() non costanti (QList è un esempio di tale container). Usando i const_iterator assicurati di stare chiamando la versione costante di begin() e end(); altrimenti, a meno che il tuo container sia esso stesso costante, potrebbero esserci detach non necessari del tuo container. Fondamentalmente ogni qual volta usi const_iterator inizializzalo usando constBegin()/constEnd(), per stare sul sicuro.

    Conserva il valore di ritorno del metodo end() (o constEnd()) prima di iterare su un grande container. Per esempio:

    QValueList<QualcheClasse> container;

    //codice che inserisce un grande numero di elementi nel container

    QValueListConstIterator end( container.constEnd() );

    for ( QValueListConstIterator itr( container.constBegin() );

        itr != end; ++itr ) {
    

    }

    Questo evita la creazione non necessaria di un oggetto temporaneo ritornato da end() (o constEnd()) ad ogni iterazione del ciclo, velocizzandolo ampiamente.

    Preferire l'uso degli incrementi prefissi piuttosto di quelli postfissi negli iteratori così da evitare inutili creazioni di oggetti temporanei nel processo.

    Fai attenzione quando cancelli elementi dentro un ciclo

    Quando vuoi cancellare qualche elemento dalla lista, vorresti usare codice simile a questo:

    <! -- approfondire timer e job -->

    QMap<int, Job *>::iterator it = m_activeTimers.begin(); QMap<int, Job *>::iterator itEnd = m_activeTimers.end();

    for( ; it!=itEnd ; ++it ) {

       if(it.value() == job)
       {
           // Trovato un timer per questo job. Fermiamolo.
           killTimer(it.key());
           m_activeTimers.erase(it);
       }
    

    }

    Questo codice può potenzialmente andare in crash a causa dell'iteratore pendente dopo la chiamata a erase(). Devi riscrivere il codice in questo modo:

    QMap<int, Job *>::iterator it = m_activeTimers.begin(); while (it != m_activeTimers.end()) {

       QMap<int, Job *>::iterator prev = it;
       ++it;
       if(prev.value() == job)
       {
           // Trovato un timer per questo job. Fermiamolo.
           killTimer(prev.key());
           m_activeTimers.erase(prev);
       }
    

    }

    Questo problema è anche discusso nella documentazione Qt di QMap::iterator ma si applica a tutti gli iteratori delle Qt.

    Falle nella memoria

    Un errore di programmazione molto "popolare" consiste nel fare un new senza un delete come in questo programma:

    mem_buongustaio.cpp class t {

     public:
       t() {}
    

    };

    void inquina() {

     t* inquinatore = new t();
    

    }

    int main() {

     while (true) inquina();
    

    }

    Come puoi vedere, inquina() istanzia un nuovo oggetto inquinatore di tipo t. Quindi, la variabile inquinatore viene persa dato che è locale, ma il contenuto (l'oggetto) rimane nello heap. Posso usare questo programma per rendere il mio computer inutilizzabile in 10 secondi.

    Per risolvere, ci sono i seguenti approcci:

    • tieni la variabile nello stack invece che nello heap:

     t* inquinatore = new t();
    

    diventerà

     t inquinatore();
    

    • cancella l'inquinatore usando la funzione complementare a new:

     delete inquinatore;
    

    -->

    Uno strumento per individuare le falle di memoria come queste è Valgrind.

    dynamic_cast

    Puoi fare un dynamic_cast al tipo T dal tipo T2 tali che:

    <! -- ricontrollare e approfondire

    • T is defined in a library you link to (you'd get a linker error if this isn't the case, since it won't find

    the vtable or RTTI info)

    • T is "well-anchored" in that library. By "well-anchored" I mean that the vtable is not a COMMON symbol subject to merging at run-time by the dynamic linker. In other words, the first virtual member in the class definition must exist and not be inlined: it must be in a .cpp file.
    • T and T2 are exported -->
    • T è definito in una libreria a cui fai il link (avrai un errore dal linker se non è così, dal momento che non troverà le informazioni vtable e RTTI)
    • T è "ben-ancorato" in quella libreria. Con "ben-ancorato" intendo che vtable non è un simbolo COMUNE soggetto a fusioni a run-time da parte del linker dinamico. In altre parole, il primo membro virtuale nella definizione della classe deve esistere e non essere inline: deve essere in un file .cpp.
    • T e T2 sono esportati.

    Per esempio, noi abbiamo incontrato qualche problema difficile da individuare nel codice C++ non KDE (NMM credo) a cui stavamo facendo il link:

    • libphonon carica il plugin NMM
    • il plugin NMM fa il link a NMM
    • NMM carica i suoi plugins
    • i plugins propri di NMM, fanno il link a NMM

    Qualche classe nella libreria di NMM non aveva vtables ben-ancorate, così il dynamic_casting falliva dentro al plugin NMM di Phonon per gli oggetti creatii nei plugins di NMM

    Progettazione delle applicazioni

    In questa sezione copriremo un po' di problemi comuni relativi alla progettazione di applicazioni Qt/KDE.

    Inizializzazione ritardata

    posticipata? Sebbene il design di moderne applicazioni C++ può essere molto complesso, un problema ricorrente, generalmente facile da sistemare, è il non usare la tecnica dell'inizializzazione ritardata.

    Per prima cosa, diamo un'occhiata al modo standard di inizializzare un'applicazione KDE:

    int main( int argc, char **argv ) {

       ....
       KApplication a;
    
       KCmdLineArgs *args = KCmdLineArgs::parsedArgs();
    
       MainWindow *window = new MainWindow( args );
    
       a.setMainWidget( window );
       window->show();
    
       return a.exec();
    

    }

    Nota che window viene creata prima di a.exec() il quale fa partire il ciclo degli eventi. Ciò implica che vogliamo evitare di fare cose non banali nella parte alta del costruttore, visto che verranno eseguite prima ancora che la finestra venga mostrata.

    La soluzione è semplice: abbiamo bisogno di ritardare la costruzione di qualunque cosa oltre alla GUI fino a che il ciclo degli eventi sia partito. Qui di seguito è mostrato come il costruttore della classe MainWindow dovrebbe essere per ottenere questo risultato:

    MainWindow::MainWindow() {

       initGUI();
       QTimer::singleShot( 0, this, SLOT(initObject()) );
    

    }

    void MainWindow::initGUI() {

       /* Costruisci i tuoi widget qui. Nota che non devono
        * richiedere una complessa inizializzazione,
        * o verrà meno lo scopo di questa tecnica.
        * Tutto ciò che vorresti fare è creare i tuoi oggetti
        * della GUI ed usare QObject::connect per connettere
        * i segnali agli slots appropriati.
        */
    

    }

    void MainWindow::initObject() {

       /* Questo slot sarà chiamato non appena parte il ciclo eventi.
        * Metti tutto il resto che deve essere fatto, compresi
        * assegnazione di valori, lettura files, ristebilire sessioni, etc...
        * Tutto ciò prenderà lo stesso del tempo, ma almeno la tua
        * finestra sarà visibile a schermo, facendo apparire
        * attiva l'applicazione.
        */
    

    }

    Usare questa tecnica potrebbe non far risparmiare del tempo in più, ma farà sembrare più veloce l'applicazione agli utenti che la eseguono. Questa percezione di reattività incrementata è rassicurante per l'utente il quale riceve un rapido feedback per il riuscito avvio dell'applicazione.

    Quando (e solo in questo caso) l'avvio non può essere reso ragionevolmente abbastanza veloce, considera l'uso di KSplashScreen.

    Strutture Dati

    In questa sezione spazieremo su alcune delle nostre più comuni persecuzioni che affliggono le più comuni strutture dati viste nelle applicazioni Qt/KDE.

    Passing non-POD types

    Non-POD ("plain old data") types should be passed by const reference if at all possible. This includes anything other than the basic types such as char and int.

    Take, for instance, QString. They should always be passed into methods as const QString&. Even though QString is implicitly shared it is still more efficient (and safer) to pass const references as opposed to objects by value.

    So the canonical signature of a method taking QString arguments is:

    void myMethod( const QString & foo, const QString & bar );

    QObject

    If you ever need to delete a QObject derived class from within one of its own methods, do not ever delete it this way:

      delete this;
    

    This will sooner or later cause a crash because a method on that object might be invoked from the Qt event loop via slots/signals after you deleted it.

    Instead always use QObject::deleteLater() which tries to do the same thing as delete this but in a safer way.

    Empty QStrings

    It is common to want to see if a QString is empty. Here are three ways of doing it, the first two of which are correct:

    // Correct if ( mystring.isEmpty() ) { }

    // Correct if ( mystring == QString() ) { }

    // Wrong! "" if ( mystring == "" ) { }

    While there is a distinction between "null" QStrings and empty ones, this is a purely historical artifact and new code is discouraged from making use of it.

    QString and reading files

    If you are reading in a file, it is faster to convert it from the local encoding to Unicode (QString) in one go, rather than line by line. This means that methods like QIODevice::readAll() are often a good solution, followed by a single QString instantiation.

    For larger files, consider reading a block of lines and then performing the conversion. That way you get the opportunity to update your GUI. This can be accomplished by reentering the event loop normally, along with using a timer to read in the blocks in the background, or by creating a local event loop.

    While one can also use qApp->processEvents(), it is discouraged as it easily leads to subtle yet often fatal problems.

    Reading QString from a KProcess

    KProcess emits the signals readyReadStandard{Output|Error} as data comes in. A common mistake is reading all available data in the connected slot and converting it to QString right away: the data comes in arbitrarily segmented chunks, so multi-byte characters might be cut into pieces and thus invalidated. Several approaches to this problem exist:

    • Do you really need to process the data as it comes in? If not, just use readAllStandard{Output|Error} after the process has exited. Unlike in KDE3, KProcess is now able to accumulate the data for you.
    • Wrap the process into a QTextStream and read line-wise. This should work starting with Qt 4.4.
    • Accumulate data chunks in the slots and process them each time a newline arrives or after some timeout passes. Example code

    QString and QByteArray

    While QString is the tool of choice for many string handling situations, there is one where it is particularly inefficient. If you are pushing about and working on data in QByteArrays, take care not to pass it through methods which take QString parameters; then make QByteArrays from them again.

    For example:

    QByteArray myData; QString myNewData = mangleData( myData );

    QString mangleData( const QString& data ) {

       QByteArray str = data.toLatin1();
       // mangle 
       return QString(str);
    

    }

    The expensive thing happening here is the conversion to QString, which does a conversion to Unicode internally. This is unnecessary because, the first thing the method does is convert it back using toLatin1(). So if you are sure that the Unicode conversion is not needed, try to avoid inadvertently using QString along the way.

    The above example should instead be written as:

    QByteArray myData; QByteArray myNewData = mangleData( myData );

    QByteArray mangleData( const QByteArray& data )

    QDomElement

    When parsing XML documents, one often needs to iterate over all the elements. You may be tempted to use the following code for that:

    for ( QDomElement e = baseElement.firstChild().toElement();

         !e.isNull();
         e = e.nextSibling().toElement() ) {
          ...
    

    }

    That is not correct though: the above loop will stop prematurely when it encounters a QDomNode that is something other than an element such as a comment.

    The correct loop looks like:

    for ( QDomNode n = baseElement.firstChild(); !n.isNull();

         n = n.nextSibling() ) {
       QDomElement e = n.toElement();
       if ( e.isNull() ) {
           continue;
       }
       ...
    

    }