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

    From KDE TechBase
    No edit summary
    No edit summary
     
    (9 intermediate revisions by one other user not shown)
    Line 1: Line 1:
    {{Template:I18n/Language Navigation Bar|Development/Tutorials/Common Programming Mistakes}}


    {{TutorialBrowser|
     
    {{TutorialBrowser_(it)|


    series=Getting Started|
    series=Getting Started|
    Line 172: Line 172:


    ==== Preferire iteratori costanti e conservare end() ====
    ==== Preferire iteratori costanti e conservare end() ====
    Preferire l'uso dei <tt>const_iterators</tt> rispetto ai normali iteratori quando possibile. I containers, implicitamente condivisi, spesso eseguono un detach() (vedi [http://doc.trolltech.com/4.4/shared.html#shared-classes] per più informazioni, n.d.t.) quando viene fatta una chiamata ad un metodo <tt>begin()</tt> o <tt>end()</tt> non costanti ({{qt|QList}} è un esempio di tale container). Usando i const_iterator viene controllato anche che stai veramente chiamando la versione costante di <tt>begin()</tt> e <tt>end()</tt>. A meno che il tuo container è esso stesso costante non sarà questo il caso, probabilmente causando un detach non necessario del tuo container. Fondamentalmente ogni qual volta usi const_iterator inizializzalo usando <tt>constBegin()</tt>/<tt>constEnd()</tt>, per stare sul sicuro.
    Preferisci l'uso dei <tt>const_iterators</tt> rispetto ai normali iteratori quando possibile. I containers, implicitamente condivisi, spesso eseguono un detach() (vedi [http://doc.trolltech.com/4.4/shared.html#shared-classes] per più informazioni, n.d.t.) quando viene fatta una chiamata ad un metodo <tt>begin()</tt> o <tt>end()</tt> non costanti ({{qt|QList}} è un esempio di tale container). Usando i const_iterator assicurati di stare chiamando la versione costante di <tt>begin()</tt> e <tt>end()</tt>; 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 <tt>constBegin()</tt>/<tt>constEnd()</tt>, per stare sul sicuro.


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


    <code cppqt>
    <code cppqt>
    QValueList<QualcheClasse> container;
    QList<QualcheClasse> container;


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


    QValueListConstIterator end( container.constEnd() );
    QList<QualcheClasse>::ConstIterator end = container.constEnd();
    QList<QualcheClasse>::ConstIterator itr = container.constBegin();


    for ( QValueListConstIterator itr( container.constBegin() );
    for ( ; itr != end; ++itr ) {
        itr != end; ++itr ) {
      // usa *itr (oppure itr.value()) qui dentro
    }
    }
    </code>
    </code>
    Line 190: Line 191:
    Questo evita la creazione non necessaria di un oggetto temporaneo ritornato da <tt>end()</tt> (o <tt>constEnd()</tt>) ad ogni iterazione del ciclo, velocizzandolo ampiamente.
    Questo evita la creazione non necessaria di un oggetto temporaneo ritornato da <tt>end()</tt> (o <tt>constEnd()</tt>) 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.
    Ogni volta che usi gli iteratori, utilizza sempre operatori di pre-incremento e pre-decremento (ad esempio, <tt>++itr</tt>) a meno di avere uno motivo specifico per non farlo. L'utilizzo di operatori di post-incrementi e post-decrementi (come <tt>itr++</tt>) causano la creazione di un oggetto temporaneo.


    ==== Fai attenzione quando cancelli elementi dentro un ciclo ====
    ==== Fai attenzione quando cancelli elementi dentro un ciclo ====


    Quando vuoi cancellare qualche elemento dalla lista, vorresti usare codice simile a questo:
    Quando vuoi cancellare qualche elemento dalla lista, vorresti usare codice simile a questo:
    <! -- <font color="red">approfondire timer e job</font> -->


    <code cppqt>
    <code cppqt>
    Line 204: Line 207:
         if(it.value() == job)
         if(it.value() == job)
         {
         {
             //Trovato un timer per questo job. Fermiamolo.
             // Trovato un timer per questo job. Fermiamolo.
             killTimer(it.key());
             killTimer(it.key());
             m_activeTimers.erase(it);
             m_activeTimers.erase(it);
    Line 222: Line 225:
         if(prev.value() == job)
         if(prev.value() == job)
         {
         {
             //Trovato un timer per questo job. Fermiamolo.
             // Trovato un timer per questo job. Fermiamolo.
             killTimer(prev.key());
             killTimer(prev.key());
             m_activeTimers.erase(prev);
             m_activeTimers.erase(prev);
    Line 268: Line 271:
    <code cppqt>  
    <code cppqt>  
       delete inquinatore;
       delete inquinatore;
    </code> -->
    </code>


    Uno strumento per individuare le falle di memoria come queste è [[Development/Tools/Valgrind|Valgrind]].
    Uno strumento per individuare le falle di memoria come queste è [[Development/Tools/Valgrind|Valgrind]].
    Line 274: Line 277:
    === dynamic_cast ===
    === dynamic_cast ===


    You can only dynamic_cast to type T from type T2 provided
    Puoi fare un dynamic_cast al tipo T dal tipo T2 tali che:
    that:


    <! -- <font color="red">ricontrollare e approfondire</font>
    * 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  
    * 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)
    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 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 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.


    For instance, we've seen some hard-to-track problems in non-KDE C++ code we're linking with (I think NMM) because of that. It happened that:
    Per esempio, noi abbiamo incontrato qualche problema difficile da individuare nel codice C++ non KDE (NMM credo) a cui stavamo facendo il link:
    * libphonon loads the NMM plugin
    * libphonon carica il plugin NMM
    * NMM plugin links to NMM
    * il plugin NMM fa il link a NMM
    * NMM loads its own plugins
    * NMM carica i suoi plugins
    * NMM's own plugins link to NMM
    * i plugins propri di NMM, fanno il link a NMM


    Some classes in the NMM library did not have well-anchored vtables, so dynamic_casting failed inside the Phonon NMM plugin for objects created in the NMM's own plugins.
    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


    == Program Design ==
    == Progettazione delle applicazioni ==


    In this section we will go over some common problems related to the design of Qt/KDE applications.
    In questa sezione copriremo un po' di problemi comuni relativi alla progettazione di applicazioni Qt/KDE.


    === Delayed Initialization ===
    === Inizializzazione ritardata ===


    Although the design of modern C++ applications can be very complex, one recurring problem, which is generally easy to fix, is not using the technique of [http://www.kdedevelopers.org/node/view/509 delayed initialization].  
    Sebbene il design di moderne applicazioni C++ può essere molto complesso, un problema ricorrente, generalmente facile da sistemare, è il non usare la tecnica dell'[http://www.kdedevelopers.org/node/509 inizializzazione ritardata].


    First, let us look at the standard way of initializing a KDE application:  
    Per prima cosa, diamo un'occhiata al modo standard di inizializzare un'applicazione KDE:


    <code cppqt>
    <code cppqt>
    Line 317: Line 323:
    </code>
    </code>
          
          
    Notice that <tt>window</tt> is created before the <tt>a.exec()</tt> call that starts the event loop. This implies that we want to avoid doing anything non-trivial in the top-level constructor, since it runs before we can even show the window.
    Nota che <tt>window</tt> viene creata prima di <tt>a.exec()</tt> 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.


    The solution is simple: we need to delay the construction of anything besides the GUI until after the event loop has started. Here is how the example class MainWindow's constructor could look to achieve this:
    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:


    <code cppqt>
    <code cppqt>
    Line 330: Line 336:
    void MainWindow::initGUI()
    void MainWindow::initGUI()
    {
    {
         /* Construct your widgets here. Note that the widgets you
         /* Costruisci i tuoi widget qui. Nota che non devono
         * construct here shouldn't require complex initialization
         * richiedere una complessa inizializzazione,
         * either, or you've defeated the purpose.
         * o verrà meno lo scopo di questa tecnica.
         * All you want to do is create your GUI objects and
         * Tutto ciò che vorresti fare è creare i tuoi oggetti
         * QObject::connect
         * della GUI ed usare QObject::connect per connettere
         * the appropriate signals to their slots.
         * i segnali agli slots appropriati.
         */
         */
    }
    }
    Line 341: Line 347:
    void MainWindow::initObject()
    void MainWindow::initObject()
    {
    {
         /* This slot will be called as soon as the event loop starts.
         /* Questo slot sarà chiamato non appena parte il ciclo eventi.
         * Put everything else that needs to be done, including
         * Metti tutto il resto che deve essere fatto, compresi
         * restoring values, reading files, session restoring, etc here.
         * assegnazione di valori, lettura files, ristebilire sessioni, etc...
         * It will still take time, but at least your window will be
         * Tutto ciò prenderà lo stesso del tempo, ma almeno la tua
         * on the screen, making your app look active.
         * finestra sarà visibile a schermo, facendo apparire
        * attiva l'applicazione.
         */
         */
    }
    }
    </code>
    </code>
     
    Using this technique may not buy you any overall time, but it makes your app ''seem'' quicker to the user who is starting it. This increased perceived responsiveness is reassuring for the user as they get quick feedback that the action of launching the app has succeeded.


    When (and only when) the start up can not be made reasonably fast enough, consider using a {{class|KSplashScreen}}.
    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 {{class|KSplashScreen}}.


    == Data Structures ==
    == Strutture Dati ==


    In this section we will go over some of our most common pet-peeves which affect data structures very commonly seen in Qt/KDE applications.
    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 ===
    === Passaggio di tipi non POD ===


    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 <tt>char</tt> and <tt>int</tt>.
    I tipi di dato diversi da POD ([http://en.wikipedia.org/wiki/Plain_old_data "Plain Old Data"], dati semplici senza la logica di controllo) dovrebbero essere passati sempre per riferimento costante. Questo include qualunque cosa tranne i tipi base come <tt>char</tt> e <tt>int</tt>.


    Take, for instance, {{qt|QString}}. They should always be passed into methods as <tt>const {{qt|QString}}&</tt>. Even though {{qt|QString}} is implicitly shared it is still more efficient (and safer) to pass const references as opposed to objects by value.  
    Prendi, per esempio, {{qt|QString}}. Dovrebbero sempre essere passati ai metodi come <tt>const {{qt|QString}}&</tt>. Anche se {{qt|QString}} è implicitamente condiviso è comunque più efficiente (e sicuro) passarlo per referenza costante piuttosto che come oggetto per valore.


    So the canonical signature of a method taking QString arguments is:
    Quindi la dichiarazione canonica di un metodo che prende QString come argomento è:


    <code cppqt>
    <code cppqt>
    void myMethod( const QString & foo, const QString & bar );
    void mioMetodo( const QString & x, const QString & y );
    </code>
    </code>


    === QObject ===
    === 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:  
    Se avrai mai bisogno di eliminare una classe derivata da QObject dall'interno di uno dei suoi metodi, non farlo mai in questo modo:


    <code cppqt>
    <code cppqt>
    Line 378: Line 385:
    </code>
    </code>


    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.
    Prima o poi ciò causerà un crash perché un metodo di quell'oggetto potrebbe essere invocato dal ciclo eventi delle Qt via slots/signals dopo che tu l'hai eliminato.


    Instead always use <tt>{{QtMethod|QObject|deleteLater}}</tt> which tries to do the same thing as <tt>delete this</tt> but in a safer way.
    Invece usa sempre <tt>{{QtMethod|QObject|deleteLater}}</tt> il quale cerca di fare la stessa cosa di <tt>delete this</tt> ma in modo più sicuro.


    === Empty QStrings ===
    === QStrings vuote ===


    It is common to want to see if a {{qt|QString}} is empty. Here are three ways of doing it, the first two of which are correct:
    Si è soliti voler verificare se un {{qt|QString}} è vuoto. Di seguito ci sono tre modi per farlo, dei quali i primi due sono corretti:


    <code cppqt>
    <code cppqt>
    // Correct
    // Corretto
    if ( mystring.isEmpty() ) {
    if ( miaStringa.isEmpty() ) {
    }
    }


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


    // Wrong! ""
    // Sbagliato! ""
    if ( mystring == "" ) {
    if ( miaStringa == "" ) {
    }
    }
    </code>
    </code>


    While there is a distinction between "null" {{qt|QString}}s and empty ones, this is a purely historical artifact and new code is discouraged from making use of it.
    Mentre c'è distinzione tra {{qt|QString}} nulle e vuote, ciò è puramente un artefatto storico e per il nuovo codice se ne scoraggia l'uso.
     
    === QString e lettura da file ===


    === QString and reading files ===
    Se stai leggendo un file, è più veloce convertirlo dalla codifica locale a Unicode ({{qt|QString}}) tutto in una volta, piuttosto che riga per riga. Questo vuol dire che metodi come <tt>{{qt|QIODevice}}::readAll()</tt> sono spesso una buona soluzione, seguiti da una singola istanza di {{qt|QString}}.


    If you are reading in a file, it is faster to convert it from the local encoding to Unicode ({{qt|QString}}) in one go, rather than line by line. This means that methods like <tt>{{qt|QIODevice}}::readAll()</tt> are often a good solution, followed by a single {{qt|QString}} instantiation.
    Per file molto grandi, considera la possibilità di leggere un blocco di righe e quindi eseguire la conversione. In questo modo hai l'opportunità di aggiornare la GUI. Ciò può essere fatto rientrando normalmente nel ciclo eventi, contemporaneamente utilizzando un timer per leggere i blocchi in background, oppure creando un ciclo eventi locale.


    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.  
    Anche se si potrebbe usare anche <tt>qApp->processEvents()</tt>, è scoraggiato siccome porta facilmente a subdoli problemi fatali.


    While one can also use <tt>qApp->processEvents()</tt>, it is discouraged as it easily leads to subtle yet often fatal problems.
    === Leggere QString da un KProcess ===


    === Reading QString from a KProcess ===
    {{class|KProcess}} emette il segnale <tt>readyReadStandard{Output|Error}</tt> appena arrivano dei dati.
    Un errore comune sta nel leggere tutti i dati disponibili nello slot connesso e convertirli in un {{qt|QString}} così come sono: i dati arrivano arbitrariamente segmentati, così caratteri multi-byte potrebbero essere tagliati in pezzi e perciò invalidati. Esistono molti approcci al problema:


    {{class|KProcess}} emits the signals <tt>readyReadStandard{Output|Error}</tt> as data comes in.
    * Hai veramente bisogno di processare i dati che arrivano? Se no, basta utilizzare <tt>readAllStandard{Output|Error}</tt> dopo che il processo è uscito. Diversamente da KDE3, KProcess è ora capace di accumulare i dati per te.
    A common mistake is reading all available data in the connected slot and converting it to {{qt|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:
    * Racchiudi il processo in un {{qt|QTextStream}} e leggi intere righe. Questo dovrebbe funzionare a partire dalle Qt 4.4
    <ul>
    * Accumula i pezzi di dati negli slots e processali ogni volta che arriva una riga e dopo un certo timeout. [http://websvn.kde.org/trunk/KDE/kdevplatform/util/processlinemaker.cpp?view=markup Codice di esempio]
    <li>Do you really need to process the data as it comes in? If not, just use <tt>readAllStandard{Output|Error}</tt> after the process has exited. Unlike in KDE3, KProcess is now able to accumulate the data for you.</li>
    <li>Wrap the process into a {{qt|QTextStream}} and read line-wise. This should work starting with Qt 4.4.</li>
    <li>Accumulate data chunks in the slots and process them each time a newline arrives or after some timeout passes. [http://websvn.kde.org/trunk/KDE/kdevplatform/util/processlinemaker.cpp?view=markup Example code]</li>
    </ul>


    === QString and QByteArray ===
    === QString e QByteArray ===


    While {{qt|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 {{qt|QByteArray}}s, take care not to pass it through methods which take {{qt|QString}} parameters; then make QByteArrays from them again.
    Mentre {{qt|QString}} è lo strumento scelto per molte situazioni che richiedono la gestione di stringhe, ce n'è una dove è particolarmente inefficiente. Se stai lavorando con dati in un {{qt|QByteArray}}, fai attenzione a non passarlo a metodi che prendono parametri di tipo {{qt|QString}}, e che ne tirano fuori un QByteArrays di nuovo.


    For example:  
    Per esempio:


    <code cppqt>
    <code cppqt>
    QByteArray myData;
    QByteArray mieiDati;
    QString myNewData = mangleData( myData );
    QString mieiNuoviDati = maciullaDati( mieiDati );


    QString mangleData( const QString& data ) {
    QString maciullaDati( const QString& dati ) {
         QByteArray str = data.toLatin1();
         QByteArray str = dati.toLatin1();
         // mangle
         // maciulla
         return QString(str);
         return QString(str);
    }
    }
    </code>
    </code>
       
    The expensive thing happening here is the conversion to {{qt|QString}}, which does a conversion to Unicode internally. This is unnecessary because, the first thing the method does is convert it back using <tt>toLatin1()</tt>. 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:
    L'operazione dispendiosa che si verifica è la conversione in {{qt|QString}}, la quale internamente fa conversioni a Unicode. Ciò è inutile perché la prima cosa che il metodo fa è riconvertirla di nuovo con <tt>toLatin1()</tt>. Quindi, se sei sicuro che la conversione a Unicode non è necessaria, cerca di evitare di usare inavvertitamente QString.
     
    L'esempio di cui sopra dovrebbe invece essere scritto come:


    <code cppqt>
    <code cppqt>
    QByteArray myData;
    QByteArray mieiDati;
    QByteArray myNewData = mangleData( myData );
    QByteArray mieiNuoviDati = maciullaDati( mieiDati );


    QByteArray mangleData( const QByteArray& data )
    QByteArray maciullaDati( const QByteArray& dati )
    </code>
    </code>


    === QDomElement ===
    === 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:  
    Quando si deve fare il paring di documenti XML, si ha bisogno di iterare su tutti gli elementi. Potresti essere tentato di usare il seguente codice:


    <code cppqt>
    <code cppqt>
    Line 459: Line 465:
    }
    }
    </code>
    </code>
     
    That is not correct though: the above loop will stop prematurely when it encounters a {{qt|QDomNode}} that is something other than an element such as a comment.


    The correct loop looks like:  
    Ebbene non è corretto: questo ciclo si fermerà prematuramente non appena incontra un {{qt|QDomNode}} che è qualcosa di diverso da un elemento, come un commento.
     
    Il ciclo corretto è qualcosa di simile:


    <code cppqt>
    <code cppqt>

    Latest revision as of 16:49, 15 July 2012


    Errori di Programmazione Comuni
    Collezione di Tutorial   Getting Started
    Prerequisiti   Nessuno
    A  Seguire   n/a
    Ulteriori Letture   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:

    QList<QualcheClasse> container;

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

    QList<QualcheClasse>::ConstIterator end = container.constEnd(); QList<QualcheClasse>::ConstIterator itr = container.constBegin();

    for ( ; itr != end; ++itr ) {

     // usa *itr (oppure itr.value()) qui dentro
    

    }

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

    Ogni volta che usi gli iteratori, utilizza sempre operatori di pre-incremento e pre-decremento (ad esempio, ++itr) a meno di avere uno motivo specifico per non farlo. L'utilizzo di operatori di post-incrementi e post-decrementi (come itr++) causano la creazione di un oggetto temporaneo.

    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

    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.

    Passaggio di tipi non POD

    I tipi di dato diversi da POD ("Plain Old Data", dati semplici senza la logica di controllo) dovrebbero essere passati sempre per riferimento costante. Questo include qualunque cosa tranne i tipi base come char e int.

    Prendi, per esempio, QString. Dovrebbero sempre essere passati ai metodi come const QString&. Anche se QString è implicitamente condiviso è comunque più efficiente (e sicuro) passarlo per referenza costante piuttosto che come oggetto per valore.

    Quindi la dichiarazione canonica di un metodo che prende QString come argomento è:

    void mioMetodo( const QString & x, const QString & y );

    QObject

    Se avrai mai bisogno di eliminare una classe derivata da QObject dall'interno di uno dei suoi metodi, non farlo mai in questo modo:

      delete this;
    

    Prima o poi ciò causerà un crash perché un metodo di quell'oggetto potrebbe essere invocato dal ciclo eventi delle Qt via slots/signals dopo che tu l'hai eliminato.

    Invece usa sempre QObject::deleteLater() il quale cerca di fare la stessa cosa di delete this ma in modo più sicuro.

    QStrings vuote

    Si è soliti voler verificare se un QString è vuoto. Di seguito ci sono tre modi per farlo, dei quali i primi due sono corretti:

    // Corretto if ( miaStringa.isEmpty() ) { }

    // Corretto if ( miaStringa == QString() ) { }

    // Sbagliato! "" if ( miaStringa == "" ) { }

    Mentre c'è distinzione tra QString nulle e vuote, ciò è puramente un artefatto storico e per il nuovo codice se ne scoraggia l'uso.

    QString e lettura da file

    Se stai leggendo un file, è più veloce convertirlo dalla codifica locale a Unicode (QString) tutto in una volta, piuttosto che riga per riga. Questo vuol dire che metodi come QIODevice::readAll() sono spesso una buona soluzione, seguiti da una singola istanza di QString.

    Per file molto grandi, considera la possibilità di leggere un blocco di righe e quindi eseguire la conversione. In questo modo hai l'opportunità di aggiornare la GUI. Ciò può essere fatto rientrando normalmente nel ciclo eventi, contemporaneamente utilizzando un timer per leggere i blocchi in background, oppure creando un ciclo eventi locale.

    Anche se si potrebbe usare anche qApp->processEvents(), è scoraggiato siccome porta facilmente a subdoli problemi fatali.

    Leggere QString da un KProcess

    KProcess emette il segnale readyReadStandard{Output|Error} appena arrivano dei dati. Un errore comune sta nel leggere tutti i dati disponibili nello slot connesso e convertirli in un QString così come sono: i dati arrivano arbitrariamente segmentati, così caratteri multi-byte potrebbero essere tagliati in pezzi e perciò invalidati. Esistono molti approcci al problema:

    • Hai veramente bisogno di processare i dati che arrivano? Se no, basta utilizzare readAllStandard{Output|Error} dopo che il processo è uscito. Diversamente da KDE3, KProcess è ora capace di accumulare i dati per te.
    • Racchiudi il processo in un QTextStream e leggi intere righe. Questo dovrebbe funzionare a partire dalle Qt 4.4
    • Accumula i pezzi di dati negli slots e processali ogni volta che arriva una riga e dopo un certo timeout. Codice di esempio

    QString e QByteArray

    Mentre QString è lo strumento scelto per molte situazioni che richiedono la gestione di stringhe, ce n'è una dove è particolarmente inefficiente. Se stai lavorando con dati in un QByteArray, fai attenzione a non passarlo a metodi che prendono parametri di tipo QString, e che ne tirano fuori un QByteArrays di nuovo.

    Per esempio:

    QByteArray mieiDati; QString mieiNuoviDati = maciullaDati( mieiDati );

    QString maciullaDati( const QString& dati ) {

       QByteArray str = dati.toLatin1();
       // maciulla
       return QString(str);
    

    }

    L'operazione dispendiosa che si verifica è la conversione in QString, la quale internamente fa conversioni a Unicode. Ciò è inutile perché la prima cosa che il metodo fa è riconvertirla di nuovo con toLatin1(). Quindi, se sei sicuro che la conversione a Unicode non è necessaria, cerca di evitare di usare inavvertitamente QString.

    L'esempio di cui sopra dovrebbe invece essere scritto come:

    QByteArray mieiDati; QByteArray mieiNuoviDati = maciullaDati( mieiDati );

    QByteArray maciullaDati( const QByteArray& dati )

    QDomElement

    Quando si deve fare il paring di documenti XML, si ha bisogno di iterare su tutti gli elementi. Potresti essere tentato di usare il seguente codice:

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

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

    }

    Ebbene non è corretto: questo ciclo si fermerà prematuramente non appena incontra un QDomNode che è qualcosa di diverso da un elemento, come un commento.

    Il ciclo corretto è qualcosa di simile:

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

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

    }