Development/Tutorials/Common Programming Mistakes/pt-br: Difference between revisions

From KDE TechBase
(Created page with "=== Problemas de ponteiro NULL ===")
(Created page with "Portanto, a assinatura canônica de um método que usa argumentos QString é:")
 
(92 intermediate revisions by the same user not shown)
Line 20: Line 20:
Os símbolos definidos em um anonymous namespace C++ não têm ligação interna. Os anonymous namespaces fornecem apenas um nome exclusivo para essa unidade de tradução e é isso; eles não alteram a ligação do símbolo. A ligação não é alterada naquelas porque a segunda fase da pesquisa de nome em duas fases ignora as funções com ligações internas. Além disso, entidades com ligação interna não podem ser usadas como argumentos de template.  
Os símbolos definidos em um anonymous namespace C++ não têm ligação interna. Os anonymous namespaces fornecem apenas um nome exclusivo para essa unidade de tradução e é isso; eles não alteram a ligação do símbolo. A ligação não é alterada naquelas porque a segunda fase da pesquisa de nome em duas fases ignora as funções com ligações internas. Além disso, entidades com ligação interna não podem ser usadas como argumentos de template.  


Portanto, por enquanto, em vez de usar anonymous namespaces, use static se você não quiser que um símbolo seja exportado.
Portanto, dessa vez, ao invés de usar anonymous namespaces, use static se você não quiser que um símbolo seja exportado.


=== Problemas de ponteiro NULL ===
=== Problemas de ponteiro NULL ===


First and foremost: it is fine to delete a null pointer. So constructs like this that check for null before deleting are simply redundant:  
Em primeiro lugar: tudo bem excluir um ponteiro nulo. Portanto, construções como esta que verificam se há nulo antes de excluir são simplesmente redundantes:  
<syntaxhighlight lang="cpp-qt">
<syntaxhighlight lang="cpp-qt">
if ( ptr ) {
if ( ptr ) {
Line 30: Line 30:
}
}
</syntaxhighlight>
</syntaxhighlight>
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.
Observe, no entanto, que '''a null check ''é'' necessária quando você exclui uma matriz''' - isso ocorre porque um compilador relativamente recente no Solaris não lida com isso adequadamente.


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:  
Ao excluir um ponteiro, certifique-se de defini-lo também como 0, para que futuras tentativas de excluir esse objeto não falhem em uma exclusão dupla. Portanto, a maneira completa e adequada é:  
<syntaxhighlight lang="cpp-qt">
<syntaxhighlight lang="cpp-qt">
delete ptr;  
delete ptr;  
ptr = 0;
ptr = 0;
</syntaxhighlight>
</syntaxhighlight>
You may notice that null pointers are marked variously in one of four ways: 0, 0L, NULL and nullptr. In C, NULL is defined as a null void pointer. In C++, more type safety is possible due to stricter type checking. Modern C++11 implementations (and all C++14 implementations) define NULL to equal the special value nullptr. Nullptr can be automatically cast to boolean false, but a cast to an integer type will fail. This is useful to avoid accidentally. Older C++ implementations before c++11 simply defined NULL to 0L or 0, which provides no additional type safety - one could assign it to an integer variable, which is obviously wrong. For code which does not need to support outdated compilers the best choice is nullptr.
Você pode perceber que ponteiros nulos são variavelmente marcados em uma das quatro maneiras: 0, 0L, NULL e nullptr. Em C, NULL é definido como um ponteiro void. No C ++, é possível maior segurança devido a uma verificação mais rigorosa de tipo. As implementações modernas do C++11 (e todas as implementações do C++14) definem NULL para igual ao valor especial nullptr. Nullptr pode ser convertido automaticamente para booleano false, mas uma conversão para um tipo inteiro falhará. Isso é útil para evitar acidentes. As implementações mais antigas do C++ anteriores ao c++11 simplesmente definiam NULL como 0L ou 0, o que não fornece segurança de tipo adicional - é possível atribuí-lo a uma variável inteira, o que está obviamente errado. Para código que não precisa suportar compiladores desatualizados, a melhor opção é nullptr.


In pointer context, the integer constant zero means "null pointer" - irrespective of the actual binary representation of a null pointer. 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.
No contexto de ponteiro, a constante inteira zero significa "ponteiro nulo" - independentemente da representação binária real de um ponteiro nulo. Observe, no entanto, que se você deseja passar uma constante de ponteiro nulo para uma função em uma lista de variáveis como argumentos, você *deve* convertê-la explicitamente em um ponteiro - o compilador assume o contexto inteiro por padrão, o que pode ou não corresponder a representação binária de um ponteiro.


=== Member variables ===
=== Variáveis membro ===


You will encounter four major styles of marking class member variables in KDE, besides unmarked members:
Você encontrará quatro estilos principais de marcação de variáveis membro de classe no KDE, além de membros não marcados:


* '''m_variable''' lowercase m, underscore and the name of the variable starting with a lowercase letter. This is the most common style and one preferred for code in kdelibs.
* '''m_variable''' m minúsculo, sublinhado e o nome da variável começando com uma letra minúscula. Este é o estilo mais comum e o preferido para o código no kdelibs.


* '''mVariable''' lowercase m and the name of variable starting with an uppercase letter
* '''mVariable''' m minúsculo e o nome da variável iniciada com uma letra maiúscula


* '''variable_''' variable name starting with a lowercase letter and then an underscore
* ''' variable_''' nome da variável começando com uma letra minúscula e, em seguida, um sublinhado


* '''_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.
* '''_variable''' sublinhado e o nome da variável começando com uma letra minúscula. Esse estilo geralmente é desaprovado, pois essa notação também é usada em algum código para parâmetros de função.


Unmarked members are more common in the case of classes that use [[Policies/Library Code Policy#D-Pointers|d-pointers]].
Membros não marcados são mais comuns no caso de classes que usam [[Policies/Library Code Policy#D-Pointers|d-pointers]].


As it often happens there is no one correct way of doing it, so remember to always follow the syntax used by the application/library to which you are committing. If you're creating a new file, you may want to follow the coding style of the library or module you're adding the file to.
Como muitas vezes acontece, não há uma maneira correta de fazer; lembre-se de sempre seguir a sintaxe usada pelo aplicativo/biblioteca com a qual você está se comprometendo. Se você estiver criando um novo arquivo, siga o estilo de codificação da biblioteca ou módulo ao qual está adicionando o arquivo.


Note that symbols starting with undercores are reserved to the C library (underscore followed by capital or double underscore are reserved to the compiler), so if you can, avoid using the last type.
Observe que os símbolos que começam com sublinhados são reservados para a biblioteca C (sublinhado seguido por Maiúscula ou sublinhado duplo são reservados ao compilador); portanto, se você puder, evite usar o último tipo.


=== Static variables ===
=== Variáveis estáticas ===


Try to limit the number of static variables used in your code, especially when committing to a library. Construction and initialization of a large number of static variables really hurts the startup times.
Tente limitar o número de variáveis estáticas usadas no seu código, especialmente ao comitar para uma biblioteca. A construção e a inicialização de um grande número de variáveis estáticas realmente prejudicam os tempos de inicialização.


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.
Não use variáveis estáticas de classe, especialmente em bibliotecas e módulos carregáveis, mesmo que isso seja desencorajado em aplicativos. Objetos estáticos levam a muitos problemas, como falhas difíceis de depurar devido a ordem indefinida de construção/destruição.


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:
Em vez disso, use um ponteiro estático, junto com <tt>K_GLOBAL_STATIC</tt>, definido em <tt>kglobal.h</tt> e usado da seguinte forma:
<syntaxhighlight lang="cpp-qt">
<syntaxhighlight lang="cpp-qt">
class A { ... };
class A { ... };
Line 91: Line 91:
}
}
</syntaxhighlight>
</syntaxhighlight>
See the [http://api.kde.org/4.x-api/kdelibs-apidocs/kdecore/html/group__KDEMacros.html#ga75ca0c60b03dc5e4f9427263bf4043c7 API documentation] for <tt>K_GLOBAL_STATIC</tt> for more information.
Consulte a [http://api.kde.org/4.x-api/kdelibs-apidocs/kdecore/html/group__KDEMacros.html#ga75ca0c60b03dc5e4f9427263bf4043c7 API documentation] para <tt>K_GLOBAL_STATIC</tt> para obter mais informações.


=== Constant data ===
=== Dados Constantes ===


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 você precisar de alguns dados constantes do tipo de dado simples em vários locais, faça melhor definindo-os uma vez em um local central, para evitar um erro de digitação em uma das instâncias. Se os dados mudarem, também há apenas um lugar que você precisa editar.


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.
Mesmo que exista apenas uma instância, você faz bem definindo-o em outro lugar, para evitar os chamados "números mágicos" no código que são inexplicáveis (cf. 42). Geralmente, isso é feito na parte superior de um arquivo para evitar procurá-lo.


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.
Defina os dados constantes usando os construtores da linguagem C ++, não as instruções do pré-processador, como você pode estar acostumado a partir do C puro. Dessa maneira, o compilador pode ajudá-lo a encontrar erros fazendo a verificação de tipo.


<syntaxhighlight lang="cpp-qt">
<syntaxhighlight lang="cpp-qt">
Line 109: Line 109:




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 definir uma matriz constante, não use um ponteiro como tipo de dados. Em vez disso, use o tipo de dados e anexe à matriz o símbolo com comprimento indefinido, <tt> [] </tt>, atrás do nome. Caso contrário, você também define uma variável para alguns dados const. Essa variável pode ser atribuída por engano a um novo ponteiro, sem que o compilador se queixe. E ao acessar a matriz teria um engano, porque primeiro o valor da variável precisa ser lido.


<syntaxhighlight lang="cpp-qt">
<syntaxhighlight lang="cpp-qt">
Line 120: Line 120:
</syntaxhighlight>
</syntaxhighlight>


=== Forward Declarations ===
=== Declarações Forward ===


You will reduce compile times by forward declaring classes when possible instead of including their respective headers. The rules for when a type can be used without being defined are a bit subtle, but intuitively, if the only important aspect is the name of the class, not the details of its implementation, a forward declaration is permissible. Two examples are when declaring pointers to the class or using the class as a function argument.   
Você reduzirá o tempo de compilação ao declarar classes forward quando possível, em vez de incluir seus respectivos cabeçalhos. As regras para quando um tipo pode ser usado sem ser definido são um pouco sutis, mas intuitivamente, se o único aspecto importante é o nome da classe, não os detalhes de sua implementação, uma declaração forward é permitida. São dois exemplos ao declarar ponteiros para a classe ou ao usar a classe como argumento de função.   


For example:  
Por exemplo:  
<syntaxhighlight lang="cpp-qt">
<syntaxhighlight lang="cpp-qt">
#include <QWidget>    // slow
#include <QWidget>    // slow
Line 140: Line 140:
};
};
</syntaxhighlight>   
</syntaxhighlight>   
The above should instead be written like this:
O trecho acima deve ser escrito assim:
<syntaxhighlight lang="cpp-qt">
<syntaxhighlight lang="cpp-qt">
class QWidget;    // fast
class QWidget;    // fast
Line 156: Line 156:
};
};
</syntaxhighlight>
</syntaxhighlight>
=== Iterators ===
=== Iteração ===


==== Prefer const iterators and cache end() ====
==== Preferir iteradores const e 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.  
Prefira usar <tt>const_iterators</tt> sobre iteradores normais, quando possível. Os contêineres que estão sendo compartilhados implicitamente geralmente são desconectados quando é feita uma chamada para um método non-const <tt>begin()</tt> ou <tt>end()</tt> ({{qt|QList}} um exemplo desse contêiner). Ao usar um const_iterator, observe também que você está realmente chamando a versão const de <tt>begin()</tt> e <tt>end()</tt>. A menos que seu contêiner seja realmente const, provavelmente não será esse o caso, possivelmente causando uma desconexão desnecessária do contêiner. Então, basicamente, sempre que você usa const_iterator, inicialize-os usando <tt>constBegin()</tt>/<tt> constEnd()</tt> em vez disso, para estar seguro.  


Cache the return of the <tt>end()</tt> (or <tt>constEnd()</tt>) method call before doing iteration over large containers. For example:
Coloque em cache o retorno da chamada do método <tt>end()</tt> (ou <tt>constEnd()</tt>) antes de fazer a iteração em contêineres grandes. Por exemplo:
<syntaxhighlight lang="cpp-qt">
<syntaxhighlight lang="cpp-qt">
QList<SomeClass> container;
QList<SomeClass> container;
Line 175: Line 175:
}
}
</syntaxhighlight>
</syntaxhighlight>
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.
Isso evita a criação desnecessária do objeto de retorno temporário <tt>end()</tt> (ou <tt>constEnd()</tt>) em cada iteração de loop, acelerando-a bastante.


When using iterators, always use pre-increment and pre-decrement operators (i.e., <tt>++itr</tt>) unless you have a specific reason not to. The use of post-increment and post-decrement operators (i.e., <tt>itr++</tt>) cause the creation of a temporary object.
Ao usar iteradores, sempre use operadores de pré-incremento e pré-decremento (ou seja, <tt>++itr</tt>), a menos que você tenha um motivo específico para não fazê-lo. O uso de operadores pós-incremento e pós-decremento (isto é, <tt>itr++</tt>) causa a criação de um objeto temporário.


====Take care when erasing elements inside a loop====
==== Tenha cuidado ao apagar elementos dentro de um loop ====


When you want to erase some elements from the list, you maybe would use code similar to this:
Quando você deseja apagar alguns elementos da lista, você poderia talvez usar um código semelhante a este:
<syntaxhighlight lang="cpp-qt">
<syntaxhighlight lang="cpp-qt">
QMap<int, Job *>::iterator it = m_activeTimers.begin();
QMap<int, Job *>::iterator it = m_activeTimers.begin();
Line 194: Line 194:
}
}
</syntaxhighlight>
</syntaxhighlight>
This code will potentially crash because it is a dangling iterator after the call to erase().
Esse código vai potencialmente travar porque é um iterador pendente após a chamada para erase().
You have to rewrite the code this way:
Você precisa reescrever o código desta maneira:
<syntaxhighlight lang="cpp-qt">
<syntaxhighlight lang="cpp-qt">
QMap<int, Job *>::iterator it = m_activeTimers.begin();
QMap<int, Job *>::iterator it = m_activeTimers.begin();
Line 208: Line 208:
}
}
</syntaxhighlight>
</syntaxhighlight>
This problem is also discussed in the [https://doc.qt.io/qt-5/qmap-iterator.html#details Qt documentation for QMap::iterator] but applies to '''all''' Qt iterators
Esse problema também é discutido na [https://doc.qt.io/qt-5/qmap-iterator.html#details Qt documentation for QMap::iterator], mas se aplica a '''todos''' iteradores Qt


=== memory leaks ===
=== vazamentos de memória ===


A very "popular" programming mistake is to do a <tt>new</tt> without a <tt>delete</tt> like in this program:
Um erro de programação muito "popular" é fazer um <tt>new</tt> sem um <tt>delete</tt> como neste programa:
'''mem_gourmet.cpp'''
'''mem_gourmet.cpp'''
<syntaxhighlight lang="cpp-qt">
<syntaxhighlight lang="cpp-qt">
Line 231: Line 231:
}
}
</syntaxhighlight>
</syntaxhighlight>
You see, ''pollute()'' instantiates 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.
Veja bem, ''pollute()'' instancia um novo objeto ''polluter'' da classe ''t''. Em seguida, a variável ''polluter'' é perdida porque é local, mas o conteúdo (o objeto) permanece no heap. Eu poderia usar este programa para tornar meu computador inutilizável em 10 segundos.


To solve this, there are the following approaches:
Para resolver isso, existem as seguintes abordagens:


* keep the variable on the stack instead of the heap:
* mantenha a variável na pilha em vez do heap:
<syntaxhighlight lang="cpp-qt">
<syntaxhighlight lang="cpp-qt">
t* polluter = new t();
t* polluter = new t();
</syntaxhighlight>
</syntaxhighlight>
would become
se tornaria
<syntaxhighlight lang="cpp-qt">  
<syntaxhighlight lang="cpp-qt">  
t polluter;
t polluter;
</syntaxhighlight>
</syntaxhighlight>
* delete polluter using the complementing function to new:
* exclua o polluter usando a função complementar para new:
<syntaxhighlight lang="cpp-qt">  
<syntaxhighlight lang="cpp-qt">  
delete polluter;
delete polluter;
</syntaxhighlight>
</syntaxhighlight>
* stop the polluter in an [http://en.cppreference.com/w/cpp/memory/unique_ptr] (which will automatically delete the polluter when returning from the method)
* interrompa o polluter em um [http://en.cppreference.com/w/cpp/memory/unique_ptr] (que excluirá automaticamente o polluter ao retornar do método)
<syntaxhighlight lang="cpp-qt">
<syntaxhighlight lang="cpp-qt">
  std::unique_ptr<t> polluter = new t();
  std::unique_ptr<t> polluter = new t();
</syntaxhighlight>
</syntaxhighlight>
There's also std::shared_ptr and QSharedPointer. This is the generally preferred way to do it in modern C++; explicit memory management should be avoided when possible.
Há também o std::shared_ptr e QSharedPointer. Essa é a maneira geralmente preferida de fazê-lo no C++ moderno; o gerenciamento explícito da memória deve ser evitado quando possível.


Qt code involving QObject generally uses parent/child relations to free allocated memory; when constructing a QObject (e.g. a widget) it can be given a parent, and when the parent is deleted it deletes all its children. The parent is also set when you add a widget to a layout, for example.
O código Qt envolvendo QObject geralmente usa relações parent/child para liberar memória alocada; ao construir um QObject (por exemplo, um widget), ele pode receber um parent e, quando o parent é excluído, ele exclui todos os childs. O parent também é definido quando você adiciona um widget a um layout, por exemplo.


A tool to detect memory leaks like this is [[Development/Tools/Valgrind|Valgrind]].
Uma ferramenta para detectar vazamentos de memória como este é a [[Development/Tools/Valgrind|Valgrind]].


=== dynamic_cast ===
=== dynamic_cast ===


You can only dynamic_cast to type T from type T2 provided
Você só pode converter com dynamic_cast do tipo T de um tipo fornecido T2 que:
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 é definido em uma biblioteca que você linka (você receberá um erro no linkador, se esse não for o caso, pois ele não encontrará as informações da vtable ou do RTTI)


* 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 está "bem ancorado" nessa biblioteca. Por "bem ancorado", quero dizer que a vtable não é um símbolo COMUM sujeito a mesclagem em tempo de execução pelo linkador dinâmico. Em outras palavras, o primeiro membro virtual na definição de classe deve existir e não estar embutido: ele deve estar em um arquivo .cpp.


* T and T2 are exported
* T e T2 são exportados


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:
Por exemplo, vimos alguns problemas difíceis de rastrear no código C++ que não é do KDE com o qual estamos linkando (acho que o NMM) por causa disso. Aconteceu que:


* libphonon loads the NMM plugin
* libphonon carrega o plugin NMM


* NMM plugin links to NMM
* Plugin NMM linka para o NMM


* NMM loads its own plugins
* NMM carrega seus próprios plugins


* NMM's own plugins link to NMM
* Os próprios plugins do NMM linkam ao 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.
Algumas classes na biblioteca do NMM não tinham vtables bem ancoradas; portanto, dynamic_casting falhou dentro do plug-in Phonon NMM para objetos criados nos próprios plug-ins do NMM.


== Program Design ==
== Design do Programa ==


In this section we will go over some common problems related to the design of Qt/KDE applications.
Nesta seção, abordaremos alguns problemas comuns relacionados ao design de aplicativos Qt/KDE.


=== Delayed Initialization ===
=== Inicialização demorada ===


Although the design of modern C++ applications can be very complex, application windows can be loaded and displayed to the user very quickly through the technique of [http://www.kdedevelopers.org/node/509 delayed initialization]. This technique is relatively straightforward and useful at all stages of an interactive program.
Embora o design dos aplicativos C ++ modernos possa ser muito complexo, as janelas do aplicativo podem ser carregadas e exibidas ao usuário muito rapidamente através da técnica de [http://www.kdedevelopers.org/node/509 delayed initialization]. Essa técnica é relativamente direta e útil em todas as etapas de um programa interativo.


First, let us look at the standard way of initializing a KDE application:  
Primeiro, vejamos a maneira padrão de inicializar um aplicativo KDE:  
<syntaxhighlight lang="cpp-qt">
<syntaxhighlight lang="cpp-qt">
int main( int argc, char **argv )
int main( int argc, char **argv )
Line 305: Line 304:
}
}
</syntaxhighlight>
</syntaxhighlight>
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.
Observe que a <tt>janela</tt> é criada antes da chamada <tt>a.exec()</tt> que inicia o loop de eventos. Isso implica que queremos evitar fazer algo não trivial no construtor de nível superior, pois ele é executado antes que possamos mostrar a janela.


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:
A solução é simples: precisamos atrasar a construção de qualquer coisa além da GUI até depois que o loop de eventos for iniciado. Aqui está como o construtor da classe MainWindow de exemplo pode parecer para conseguir isso:


<syntaxhighlight lang="cpp-qt">
<syntaxhighlight lang="cpp-qt">
Line 338: Line 337:
</syntaxhighlight>
</syntaxhighlight>
    
    
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.
O uso dessa técnica pode não custar muito tempo para você, mas faz com que seu aplicativo pareça mais rápido para o usuário que o está iniciando. Esse aumento da capacidade de resposta percebida é reconfortante para o usuário, pois ele recebe um feedback rápido de que a ação de iniciar o aplicativo foi bem-sucedida.


When (and only when) the start up can not be made reasonably fast enough, consider using a {{class|KSplashScreen}}.
Quando (e somente quando) a inicialização não puder ser feita com rapidez suficiente, considere usar um {{class|KSplashScreen}}.


== Data Structures ==
== Estrutura de Dados ==


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.
Nesta seção, abordaremos algumas das nossas queixas mais comuns que afetam as estruturas de dados comumente vistas nos aplicativos Qt/KDE.


=== Passing non-POD types ===
=== Passando tipos 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>.
Tipos Non-POD ("plain old data") devem ser passados por const de referência sempre que possível. Isso inclui qualquer coisa além dos tipos básicos, como <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.  
Tome, por exemplo, {{qt|QString}}. Eles sempre devem ser passados para os métodos como <tt>const {{qt|QString}}&</tt>. Embora o {{qt|QString}} seja compartilhado implicitamente, ainda é mais eficiente (e mais seguro) passar referências const, em oposição a objetos por valor.  


So the canonical signature of a method taking QString arguments is:
Portanto, a assinatura canônica de um método que usa argumentos QString é:
<syntaxhighlight lang="cpp-qt">
<syntaxhighlight lang="cpp-qt">
void myMethod( const QString & foo, const QString & bar );
void myMethod( const QString & foo, const QString & bar );
Line 403: Line 402:
* Wrap the process into a {{qt|QTextStream}} and read line-wise. This should work starting with Qt 4.4.
* Wrap the process into a {{qt|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. [http://websvn.kde.org/trunk/KDE/kdevplatform/util/processlinemaker.cpp?view=markup Example code]
* Accumulate data chunks in the slots and process them each time a newline arrives or after some timeout passes. [https://cgit.kde.org/kdevelop.git/tree/kdevplatform/util/processlinemaker.cpp Example code]


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

Latest revision as of 17:35, 22 April 2020

Erros Comuns de Programação
Tutorial Series   Iniciando
Previous   None
What's Next   n/a
Further Reading   APIs to avoid

Resumo

Este tutorial tem como objetivo combinar a experiência dos desenvolvedores KDE em relação aos prós e contras dos frameworks Qt e KDE. Além de erros reais, ele também cobre coisas que não são necessariamente "bugs", mas que tornam o código mais lento ou menos legível.

C ++ em geral

Esta seção te guia por alguns dos cantos mais empoeirados do C ++ que tende a ser mal utilizado ou que as pessoas também simplesmente erram.

Anonymous namespaces vs statics

Se você possui um método em uma classe que não acessa nenhum membro e, portanto, não precisa de um objeto para operar, torne-o estático. Se, além disso, for uma função auxiliar privada que não é necessária fora do arquivo, torne-a uma função estática do arquivo. Isso esconde o símbolo completamente.

Os símbolos definidos em um anonymous namespace C++ não têm ligação interna. Os anonymous namespaces fornecem apenas um nome exclusivo para essa unidade de tradução e é isso; eles não alteram a ligação do símbolo. A ligação não é alterada naquelas porque a segunda fase da pesquisa de nome em duas fases ignora as funções com ligações internas. Além disso, entidades com ligação interna não podem ser usadas como argumentos de template.

Portanto, dessa vez, ao invés de usar anonymous namespaces, use static se você não quiser que um símbolo seja exportado.

Problemas de ponteiro NULL

Em primeiro lugar: tudo bem excluir um ponteiro nulo. Portanto, construções como esta que verificam se há nulo antes de excluir são simplesmente redundantes:

if ( ptr ) {
   delete ptr;
}

Observe, no entanto, que a null check é necessária quando você exclui uma matriz - isso ocorre porque um compilador relativamente recente no Solaris não lida com isso adequadamente.

Ao excluir um ponteiro, certifique-se de defini-lo também como 0, para que futuras tentativas de excluir esse objeto não falhem em uma exclusão dupla. Portanto, a maneira completa e adequada é:

delete ptr; 
ptr = 0;

Você pode perceber que ponteiros nulos são variavelmente marcados em uma das quatro maneiras: 0, 0L, NULL e nullptr. Em C, NULL é definido como um ponteiro void. No C ++, é possível maior segurança devido a uma verificação mais rigorosa de tipo. As implementações modernas do C++11 (e todas as implementações do C++14) definem NULL para igual ao valor especial nullptr. Nullptr pode ser convertido automaticamente para booleano false, mas uma conversão para um tipo inteiro falhará. Isso é útil para evitar acidentes. As implementações mais antigas do C++ anteriores ao c++11 simplesmente definiam NULL como 0L ou 0, o que não fornece segurança de tipo adicional - é possível atribuí-lo a uma variável inteira, o que está obviamente errado. Para código que não precisa suportar compiladores desatualizados, a melhor opção é nullptr.

No contexto de ponteiro, a constante inteira zero significa "ponteiro nulo" - independentemente da representação binária real de um ponteiro nulo. Observe, no entanto, que se você deseja passar uma constante de ponteiro nulo para uma função em uma lista de variáveis como argumentos, você *deve* convertê-la explicitamente em um ponteiro - o compilador assume o contexto inteiro por padrão, o que pode ou não corresponder a representação binária de um ponteiro.

Variáveis membro

Você encontrará quatro estilos principais de marcação de variáveis membro de classe no KDE, além de membros não marcados:

  • m_variable m minúsculo, sublinhado e o nome da variável começando com uma letra minúscula. Este é o estilo mais comum e o preferido para o código no kdelibs.
  • mVariable m minúsculo e o nome da variável iniciada com uma letra maiúscula
  • variable_ nome da variável começando com uma letra minúscula e, em seguida, um sublinhado
  • _variable sublinhado e o nome da variável começando com uma letra minúscula. Esse estilo geralmente é desaprovado, pois essa notação também é usada em algum código para parâmetros de função.

Membros não marcados são mais comuns no caso de classes que usam d-pointers.

Como muitas vezes acontece, não há uma maneira correta de fazer; lembre-se de sempre seguir a sintaxe usada pelo aplicativo/biblioteca com a qual você está se comprometendo. Se você estiver criando um novo arquivo, siga o estilo de codificação da biblioteca ou módulo ao qual está adicionando o arquivo.

Observe que os símbolos que começam com sublinhados são reservados para a biblioteca C (sublinhado seguido por Maiúscula ou sublinhado duplo são reservados ao compilador); portanto, se você puder, evite usar o último tipo.

Variáveis estáticas

Tente limitar o número de variáveis estáticas usadas no seu código, especialmente ao comitar para uma biblioteca. A construção e a inicialização de um grande número de variáveis estáticas realmente prejudicam os tempos de inicialização.

Não use variáveis estáticas de classe, especialmente em bibliotecas e módulos carregáveis, mesmo que isso seja desencorajado em aplicativos. Objetos estáticos levam a muitos problemas, como falhas difíceis de depurar devido a ordem indefinida de construção/destruição.

Em vez disso, use um ponteiro estático, junto com K_GLOBAL_STATIC, definido em kglobal.h e usado da seguinte forma:

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

Consulte a API documentation para K_GLOBAL_STATIC para obter mais informações.

Dados Constantes

Se você precisar de alguns dados constantes do tipo de dado simples em vários locais, faça melhor definindo-os uma vez em um local central, para evitar um erro de digitação em uma das instâncias. Se os dados mudarem, também há apenas um lugar que você precisa editar.

Mesmo que exista apenas uma instância, você faz bem definindo-o em outro lugar, para evitar os chamados "números mágicos" no código que são inexplicáveis (cf. 42). Geralmente, isso é feito na parte superior de um arquivo para evitar procurá-lo.

Defina os dados constantes usando os construtores da linguagem C ++, não as instruções do pré-processador, como você pode estar acostumado a partir do C puro. Dessa maneira, o compilador pode ajudá-lo a encontrar erros fazendo a verificação de tipo.

// Correct!
static const int AnswerToAllQuestions = 42;
// Wrong!
#define AnswerToAllQuestions 42


Se definir uma matriz constante, não use um ponteiro como tipo de dados. Em vez disso, use o tipo de dados e anexe à matriz o símbolo com comprimento indefinido, [] , atrás do nome. Caso contrário, você também define uma variável para alguns dados const. Essa variável pode ser atribuída por engano a um novo ponteiro, sem que o compilador se queixe. E ao acessar a matriz teria um engano, porque primeiro o valor da variável precisa ser lido.

// Correct!
static const char SomeString[] = "Example";
// Wrong!
static const char* SomeString = "Example";
// Wrong!
#define SomeString "Example"

Declarações Forward

Você reduzirá o tempo de compilação ao declarar classes forward quando possível, em vez de incluir seus respectivos cabeçalhos. As regras para quando um tipo pode ser usado sem ser definido são um pouco sutis, mas intuitivamente, se o único aspecto importante é o nome da classe, não os detalhes de sua implementação, uma declaração forward é permitida. São dois exemplos ao declarar ponteiros para a classe ou ao usar a classe como argumento de função.

Por exemplo:

#include <QWidget>     // slow
#include <QStringList> // slow
#include <QString>     // slow
#include <QIcon>      //slow
class SomeClass
{
public:
    virtual void widgetAction( QWidget *widget ) =0;
    virtual void stringAction( const QString& str ) =0;
    virtual void stringListAction( const QStringList& strList ) =0;
private: 
    QIcon *icon;
};

O trecho acima deve ser escrito assim:

class QWidget;     // fast
class QStringList; // fast
class QString;     // fast
class QIcon;      // fast
class SomeClass
{
public:
    virtual void widgetAction( QWidget *widget ) =0;
    virtual void stringAction( const QString& str ) =0;
    virtual void stringListAction( const QStringList& strList ) =0;
private: 
    QIcon *icon;
};

Iteração

Preferir iteradores const e cache end()

Prefira usar const_iterators sobre iteradores normais, quando possível. Os contêineres que estão sendo compartilhados implicitamente geralmente são desconectados quando é feita uma chamada para um método non-const begin() ou end() (QList um exemplo desse contêiner). Ao usar um const_iterator, observe também que você está realmente chamando a versão const de begin() e end(). A menos que seu contêiner seja realmente const, provavelmente não será esse o caso, possivelmente causando uma desconexão desnecessária do contêiner. Então, basicamente, sempre que você usa const_iterator, inicialize-os usando constBegin()/ constEnd() em vez disso, para estar seguro.

Coloque em cache o retorno da chamada do método end() (ou constEnd()) antes de fazer a iteração em contêineres grandes. Por exemplo:

QList<SomeClass> container;

//code which inserts a large number of elements to the container

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

for ( ; itr != end; ++itr ) {
    // use *itr (or itr.value()) here
}

Isso evita a criação desnecessária do objeto de retorno temporário end() (ou constEnd()) em cada iteração de loop, acelerando-a bastante.

Ao usar iteradores, sempre use operadores de pré-incremento e pré-decremento (ou seja, ++itr), a menos que você tenha um motivo específico para não fazê-lo. O uso de operadores pós-incremento e pós-decremento (isto é, itr++) causa a criação de um objeto temporário.

Tenha cuidado ao apagar elementos dentro de um loop

Quando você deseja apagar alguns elementos da lista, você poderia talvez usar um código semelhante a este:

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

Esse código vai potencialmente travar porque é um iterador pendente após a chamada para erase(). Você precisa reescrever o código desta maneira:

QMap<int, Job *>::iterator it = m_activeTimers.begin();
while (it != m_activeTimers.end()) {
    if(it.value() == job) {
        //A timer for this job has been found. Let's stop it.
        killTimer(it.key());
        it = m_activeTimers.erase(it);
    } else {
        ++it;
    }
}

Esse problema também é discutido na Qt documentation for QMap::iterator, mas se aplica a todos iteradores Qt

vazamentos de memória

Um erro de programação muito "popular" é fazer um new sem um delete como neste programa: mem_gourmet.cpp

class t
{
    public:
      t() {}
};

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

int main()
{
    while (true) pollute();
}

Veja bem, pollute() instancia um novo objeto polluter da classe t. Em seguida, a variável polluter é perdida porque é local, mas o conteúdo (o objeto) permanece no heap. Eu poderia usar este programa para tornar meu computador inutilizável em 10 segundos.

Para resolver isso, existem as seguintes abordagens:

  • mantenha a variável na pilha em vez do heap:
t* polluter = new t();

se tornaria

 
t polluter;
  • exclua o polluter usando a função complementar para new:
 
delete polluter;
  • interrompa o polluter em um [1] (que excluirá automaticamente o polluter ao retornar do método)
 std::unique_ptr<t> polluter = new t();

Há também o std::shared_ptr e QSharedPointer. Essa é a maneira geralmente preferida de fazê-lo no C++ moderno; o gerenciamento explícito da memória deve ser evitado quando possível.

O código Qt envolvendo QObject geralmente usa relações parent/child para liberar memória alocada; ao construir um QObject (por exemplo, um widget), ele pode receber um parent e, quando o parent é excluído, ele exclui todos os childs. O parent também é definido quando você adiciona um widget a um layout, por exemplo.

Uma ferramenta para detectar vazamentos de memória como este é a Valgrind.

dynamic_cast

Você só pode converter com dynamic_cast do tipo T de um tipo fornecido T2 que:

  • T é definido em uma biblioteca que você linka (você receberá um erro no linkador, se esse não for o caso, pois ele não encontrará as informações da vtable ou do RTTI)
  • T está "bem ancorado" nessa biblioteca. Por "bem ancorado", quero dizer que a vtable não é um símbolo COMUM sujeito a mesclagem em tempo de execução pelo linkador dinâmico. Em outras palavras, o primeiro membro virtual na definição de classe deve existir e não estar embutido: ele deve estar em um arquivo .cpp.
  • T e T2 são exportados

Por exemplo, vimos alguns problemas difíceis de rastrear no código C++ que não é do KDE com o qual estamos linkando (acho que o NMM) por causa disso. Aconteceu que:

  • libphonon carrega o plugin NMM
  • Plugin NMM linka para o NMM
  • NMM carrega seus próprios plugins
  • Os próprios plugins do NMM linkam ao NMM

Algumas classes na biblioteca do NMM não tinham vtables bem ancoradas; portanto, dynamic_casting falhou dentro do plug-in Phonon NMM para objetos criados nos próprios plug-ins do NMM.

Design do Programa

Nesta seção, abordaremos alguns problemas comuns relacionados ao design de aplicativos Qt/KDE.

Inicialização demorada

Embora o design dos aplicativos C ++ modernos possa ser muito complexo, as janelas do aplicativo podem ser carregadas e exibidas ao usuário muito rapidamente através da técnica de delayed initialization. Essa técnica é relativamente direta e útil em todas as etapas de um programa interativo.

Primeiro, vejamos a maneira padrão de inicializar um aplicativo 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();
}

Observe que a janela é criada antes da chamada a.exec() que inicia o loop de eventos. Isso implica que queremos evitar fazer algo não trivial no construtor de nível superior, pois ele é executado antes que possamos mostrar a janela.

A solução é simples: precisamos atrasar a construção de qualquer coisa além da GUI até depois que o loop de eventos for iniciado. Aqui está como o construtor da classe MainWindow de exemplo pode parecer para conseguir isso:

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.
     */
}

O uso dessa técnica pode não custar muito tempo para você, mas faz com que seu aplicativo pareça mais rápido para o usuário que o está iniciando. Esse aumento da capacidade de resposta percebida é reconfortante para o usuário, pois ele recebe um feedback rápido de que a ação de iniciar o aplicativo foi bem-sucedida.

Quando (e somente quando) a inicialização não puder ser feita com rapidez suficiente, considere usar um KSplashScreen.

Estrutura de Dados

Nesta seção, abordaremos algumas das nossas queixas mais comuns que afetam as estruturas de dados comumente vistas nos aplicativos Qt/KDE.

Passando tipos non-POD

Tipos Non-POD ("plain old data") devem ser passados por const de referência sempre que possível. Isso inclui qualquer coisa além dos tipos básicos, como char e int.

Tome, por exemplo, QString. Eles sempre devem ser passados para os métodos como const QString&. Embora o QString seja compartilhado implicitamente, ainda é mais eficiente (e mais seguro) passar referências const, em oposição a objetos por valor.

Portanto, a assinatura canônica de um método que usa argumentos QString é:

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