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

From KDE TechBase
No edit summary
No edit summary
Line 11: Line 11:
}}
}}


<!-- == Abstract == -->
== Prefazione ==
== Prefazione ==
<!-- This tutorial aims to combine the experience of KDE developers regarding Qt and KDE frameworks dos and don'ts. Besides actual mistakes, it also covers things which are not necessarily "bugs" but which make the code either slower or less readable. -->


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.
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.


<!-- == General C++ == -->
== C++ in generale ==
== C++ in generale ==


<!-- This section guides you through some of the more dusty corners of C++ which either tend to be misused or which people often simply get wrong. -->
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.


Questa sezione ti guida attraverso alcuni degli angoli più remoti del C++ che tendono ad essere usati male o dei quali la gente si sbaglia.
<!-- === Anonymous namespaces vs statics === -->
=== Namespace anonimi contro static ===
=== Namespace anonimi contro 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. -->
<! -- <font color="red">controllare il passaggio file-static</font>
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.
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.


<!-- 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. -->
<! -- <font color="red">un namespace anonimo non ha linkage interno?</font>
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.
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.
<!-- So for now instead of using anonymous namespaces use static if you do not want a symbol to be exported. -->


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


<!-- === NULL pointer issues === -->
=== Problemi di puntatore Nullo ===
=== Problemi di puntatore Nullo ===
<!-- First and foremost: it is fine to delete a null pointer. So constructs like this that check for null before deleting are simply redundant: -->


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:
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:
Line 52: Line 42:
}
}
</code>
</code>
<!-- Note however, that '''a null check ''is'' required when you delete an array''' - that's because a relatively recent compiler on Solaris does not handle it properly otherwise. -->


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.
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.
<!-- When you delete a pointer, make sure you also set it to 0 so that future attempts to delete that object will not fail in a double delete. So the complete and proper idiom is: -->


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 è:  
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 è:  
Line 65: Line 51:
ptr = 0;
ptr = 0;
</code>
</code>
<!-- You may notice that null pointers are marked variously in one of three ways: 0, 0L and NULL. In C, NULL is defined as a null void pointer. However, in C++, this is not possible due to stricter type checking. Therefore, modern C++ implementations define it to a "magic" null pointer constant which can be assigned to any pointer. Older C++ implementations, OTOH, simply defined it to 0L or 0, which provides no additional type safety - one could assign it to an integer variable, which is obviously wrong. -->


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.
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.
<!-- In pointer context, the integer constant zero means "null pointer" - irrespective of the actual binary representation of a null pointer. This means that the choice between 0, 0L and NULL is a question of personal style and getting used to something rather than a technical one - as far as the code in KDE's SVN goes you will see 0 used more commonly than NULL. -->


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.
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.
   
   
<!-- Note, however, that if you want to pass a null pointer constant to a function in a variable argument list, you *must* explicitly cast it to a pointer - the compiler assumes integer context by default, which might or might not match the binary representation of a pointer. Again, it does not matter whether you cast 0, 0L or NULL, but the shorter representation is generally preferred. -->
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.
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.


<!-- === Member variables === -->
=== Variabili membro ===
=== Variabili membro ===
<!-- You will encounter four major styles of marking class member variables in KDE:
* '''m_variable''' lowercase m, underscore and the name of the variable starting with a lowercase letter. This is the most common style and one prefered for code in kdelibs.
* '''mVariable''' lowercase m and the name of variable starting with a uppercase letter
* '''variable_''' variable name starting with a lowercase letter and then an underscore
* '''_variable''' underscore and the name of variable starting with a lowercase letter. This style is actually usually frowned upon as this notation is also used in some code for function parameters instead. -->


Incontrerai quattro maggiori stili per segnare le variabili membre delle classi in KDE:
Incontrerai quattro maggiori stili per segnare le variabili membre delle classi in KDE:
Line 94: Line 66:
* '''variabile_''' il nome della variabile comincia con la lettera minuscola ed alla fine un underscore.
* '''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.
* '''_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.
<!-- As it often happens there is not one correct way of doing it, so remember to always follow the syntax used by the application/library to which you are committing. -->


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.
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.


<!-- === Static variables === -->
=== Variabili statiche ===
=== Variabili statiche ===
<!-- Try to limit the number of static variables used in your code, especially when committing to a library. Construction and initialization of large number of static variables really hurts the startup times. -->


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.
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.


<!-- 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. -->
<! -- <font color="red">ricontrollare</font>
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.
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.
<!-- Instead, use a static pointer, together with <tt>K_GLOBAL_STATIC</tt> which is defined in <tt>kglobal.h</tt> and is used like this: -->


Invece, usa un puntatore statico insieme a <tt>K_GLOBAL_STATIC</tt> definito in <tt>kglobal.h</tt> ed usato in questo modo:
Invece, usa un puntatore statico insieme a <tt>K_GLOBAL_STATIC</tt> definito in <tt>kglobal.h</tt> ed usato in questo modo:
<!-- <code cppqt>
class A { ... };
K_GLOBAL_STATIC(A, globalA)
void doSomething()
{
    A *a = globalA;
    ...
}
void doSomethingElse()
{
    if (globalA.isDestroyed()) {
        return;
    }
    A *a = globalA;
    ...
}
void installPostRoutine()
{
    qAddPostRoutine(globalA.destroy);
}
</code> -->


<code cppqt>
<code cppqt>
Line 165: Line 105:
}
}
</code>
</code>
<!-- See the [http://www.englishbreakfastnetwork.org/apidocs/apidox-kde-4.0/kdelibs-apidocs/kdecore/html/kglobal_8h.html#75ca0c60b03dc5e4f9427263bf4043c7 API documentation] for <tt>K_GLOBAL_STATIC</tt> for more information. -->


Vedi la [http://www.englishbreakfastnetwork.org/apidocs/apidox-kde-4.0/kdelibs-apidocs/kdecore/html/kglobal_8h.html#75ca0c60b03dc5e4f9427263bf4043c7 documentazione delle API] per più informazioni su <tt>K_GLOBAL_STATIC</tt>.
Vedi la [http://www.englishbreakfastnetwork.org/apidocs/apidox-kde-4.0/kdelibs-apidocs/kdecore/html/kglobal_8h.html#75ca0c60b03dc5e4f9427263bf4043c7 documentazione delle API] per più informazioni su <tt>K_GLOBAL_STATIC</tt>.


<!-- === Constant data === -->
=== Dati costanti ===
=== Dati costanti ===
<!-- If you need some constant data of simple data types in several places, you do good by defining it once at a central place, to avoid a mistype in one of the instances. If the data changes there is also only one place you need to edit. -->


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.
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.
<!-- Even if there is only one instance you do good by defining it elsewhere, to avoid so-called "magic numbers" in the code which are unexplained (cmp. 42). Usually this is done at the top of a file to avoid searching for it. -->


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.
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.
<!-- Define the constant data using the language constructs of C++, not the preprocessor instructions, like you may be used to from plain C. This way the compiler can help you to find mistakes by doing type checking. -->


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.
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.
<!-- <code cppqt>
// Correct!
static const int AnswerToAllQuestions = 42;
// Wrong!
#define AnswerToAllQuestions 42
</code> -->


<code cppqt>
<code cppqt>
Line 199: Line 123:
</code>
</code>


<!-- 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, <tt>[]</tt>, 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. -->
<! -- <font color="red">ricontrollare</font>
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, <tt>[]</tt>, 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, <tt>[]</tt>, dopo il nome. Altrimenti definirai anche una variabile che punta a 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.
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, <tt>[]</tt>, 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.
 
<!-- <code cppqt>
// Correct!
static const char SomeString[] = "Example";
// Wrong!
static const char* SomeString = "Example";
// Wrong!
#define SomeString "Example"
</code> -->


<code cppqt>
<code cppqt>
Line 221: Line 137:
</code>
</code>


<!-- === Forward Declarations === -->
=== Dichiarazioni anticipate ===
=== Dichiarazioni anticipate ===
<!-- You will reduce compile times by forward declaring classes when possible instead of including their respective headers. For example: -->


Ridurrai i tempi di compilazione dichiarando anticipatamente le classi quando possibile invece di includere i rispettivi headers. Per esempio:
Ridurrai i tempi di compilazione dichiarando anticipatamente le classi quando possibile invece di includere i rispettivi headers. Per esempio:
<!-- <code cppqt>
#include <QWidget>    // slow
#include <QStringList> // slow
#include <QString>    // slow
class SomeInterface
{
public:
    virtual void widgetAction( QWidget *widget ) =0;
    virtual void stringAction( const QString& str ) =0;
    virtual void stringListAction( const QStringList& strList ) =0;
};
</code> -->


<code cppqt>
<code cppqt>
Line 254: Line 154:
</code>
</code>


<!-- The above should instead be written like this: -->
Dovrebbe invece essere scritto in questo modo:
Dovrebbe invece essere scritto in questo modo:
<!-- <code cppqt>
class QWidget;    // fast
class QStringList; // fast
class QString;    // fast
class SomeInterface
{
public:
    virtual void widgetAction( QWidget *widget ) =0;
    virtual void stringAction( const QString& str ) =0;
    virtual void stringListAction( const QStringList& strList ) =0;
};
</code> -->


<code cppqt>
<code cppqt>
Line 283: Line 169:
</code>
</code>


<!-- === Iterators === -->
=== Iteratori ===
=== Iteratori ===
<!-- ==== Prefer const iterators and cache end() ====
Prefer to use <tt>const_iterators</tt> over normal iterators when possible. Containers, which are being implicitly shared often detach when a call to a non-const <tt>begin()</tt> or <tt>end()</tt> methods is made ({{qt|QList}} is an example of such a container). When using a const_iterator also watch out that you are really calling the const version of <tt>begin()</tt> and <tt>end()</tt>. Unless your container is actually const itself this probably will not be the case, possibly causing an unnecessary detach of your container. So basically whenever you use const_iterator initialize them using <tt>constBegin()</tt>/<tt>constEnd()</tt> instead, to be on the safe side. -->


==== 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.
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.
<!-- Cache the return of the <tt>end()</tt> (or <tt>constEnd()</tt>) method call before doing iteration over large containers. For example: -->


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>
QValueList<SomeClass> container;
//code which inserts a large number of elements to the container
QValueListConstIterator end( container.constEnd() );
for ( QValueListConstIterator itr( container.constBegin() );
    itr != end; ++itr ) {
}
</code> -->


<code cppqt>
<code cppqt>
Line 319: Line 187:
}
}
</code>
</code>
<!-- This avoids the unnecessary creation of the temporary <tt>end()</tt> (or <tt>constEnd()</tt>) return object on each loop iteration, largely speeding it up. -->


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.
<!-- Prefer to use pre-increment over post-increment operators on iterators as this avoids creating an unnecessary temporary object in the process. -->


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


<!-- ====Take care when erasing elements inside a loop==== -->
==== Fai attenzione quando cancelli elementi dentro un ciclo ====
==== Fai attenzione quando cancelli elementi dentro un ciclo ====
<!-- When you want to erase some elements from the list, you maybe would use code similar to this: -->


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:
<!-- <code cppqt>
QMap<int, Job *>::iterator it = m_activeTimers.begin();
QMap<int, Job *>::iterator itEnd = m_activeTimers.end();
for( ; it!=itEnd ; ++it )
{
    if(it.value() == job)
    {
        //A timer for this job has been found. Let's stop it.
        killTimer(it.key());
        m_activeTimers.erase(it);
    }
}
</code> -->


<code cppqt>
<code cppqt>
Line 364: Line 210:
}
}
</code>
</code>
<!-- This code will potentially crash because it is a dangling iterator after the call to erase().
You have to rewrite the code this way:-->


Questo codice può potenzialmente andare in crash a causa dell'iteratore pendente dopo la chiamata a erase().
Questo codice può potenzialmente andare in crash a causa dell'iteratore pendente dopo la chiamata a erase().
Devi riscrivere il codice in questo modo:
Devi riscrivere il codice in questo modo:
<!-- <code cppqt>
QMap<int, Job *>::iterator it = m_activeTimers.begin();
while (it != m_activeTimers.end())
{
    QMap<int, Job *>::iterator prev = it;
    ++it;
    if(prev.value() == job)
    {
        //A timer for this job has been found. Let's stop it.
        killTimer(prev.key());
        m_activeTimers.erase(prev);
    }
}
</code> -->


<code cppqt>
<code cppqt>
Line 400: Line 228:
}
}
</code>
</code>
<!-- This problem is also discussed in the [http://doc.trolltech.com/4.3/qmap-iterator.html#details Qt documentation for QMap::iterator] but applies to '''all''' Qt iterators -->


Questo problema è anche discusso nella [http://doc.trolltech.com/4.3/qmap-iterator.html#details documentazione Qt di QMap::iterator] ma si applica a '''tutti''' gli iteratori delle Qt.
Questo problema è anche discusso nella [http://doc.trolltech.com/4.3/qmap-iterator.html#details documentazione Qt di QMap::iterator] ma si applica a '''tutti''' gli iteratori delle Qt.


=== memory leaks ===
=== Falle nella memoria ===


A very "popular" programming mistake is to do a <tt>new</tt> without a <tt>delete</tt> like in this program:
Un errore di programmazione molto "popolare" consiste nel fare un <tt>new</tt> senza un <tt>delete</tt> come in questo programma:


'''mem_gourmet.cpp'''
'''mem_buongustaio.cpp'''
<code cppqt>
<code cppqt>
class t
class t
Line 417: Line 243:
};
};


void pollute()
void inquina()
{
{
   t* polluter = new t();
   t* inquinatore = new t();
}
}


int main()
int main()
{
{
   while (true) pollute();
   while (true) inquina();
}
}
</code>
</code>


You see, ''pollute()'' instanciates a new object ''polluter'' of the class ''t''. Then, the variable ''polluter'' is lost because it is local, but the content (the object) stays on the heap. I could use this program to render my computer unusable within 10 seconds.
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.


To solve this, there are the following approaches:
Per risolvere, ci sono i seguenti approcci:
* keep the variable on the stack instead of the heap:
* tieni la variabile nello stack invece che nello heap:
<code cppqt>
<code cppqt>
   t* polluter = new t();
   t* inquinatore = new t();
</code>
</code>
would become
diventerà
<code cppqt>  
<code cppqt>  
   t polluter();
   t inquinatore();
</code>
</code>
* delete polluter using the complementing function to new:
* cancella l'inquinatore usando la funzione complementare a new:
<code cppqt>  
<code cppqt>  
   delete polluter;
   delete inquinatore;
</code>
</code> -->
 
Uno strumento per individuare le falle di memoria come queste è [[Development/Tools/Valgrind|Valgrind]].


A tool to detect memory leaks like this is [[Development/Tools/Valgrind|Valgrind]].
=== dynamic_cast ===
=== dynamic_cast ===



Revision as of 15:10, 27 November 2008


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

Preferire 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 viene controllato anche che stai veramente chiamando la versione costante di begin() e end(). 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 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:

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

You can only dynamic_cast to type T from type T2 provided that:

  • 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

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:

  • libphonon loads the NMM plugin
  • NMM plugin links to NMM
  • NMM loads its own plugins
  • NMM's own plugins link to 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.

Program Design

In this section we will go over some common problems related to the design of Qt/KDE applications.

Delayed Initialization

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 delayed initialization.

First, let us look at the standard way of initializing a KDE application:

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

}

Notice that window is created before the a.exec() 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.

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:

MainWindow::MainWindow() {

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

}

void MainWindow::initGUI() {

   /* Construct your widgets here.  Note that the widgets you
    * construct here shouldn't require complex initialization
    * either, or you've defeated the purpose.
    * All you want to do is create your GUI objects and
    * QObject::connect
    * the appropriate signals to their slots.
    */

}

void MainWindow::initObject() {

   /* This slot will be called as soon as the event loop starts.
    * Put everything else that needs to be done, including
    * restoring values, reading files, session restoring, etc here.
    * It will still take time, but at least your window will be
    * on the screen, making your app look active.
    */

}

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 KSplashScreen.

Data Structures

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.

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

}