portaldacalheta.pt
  • Principal
  • Noticias Do Mundo
  • Lucratividade E Eficiência
  • Ágil
  • Família
Processo Interno

Os 10 erros mais comuns de C ++ que os desenvolvedores cometem



Existem muitas armadilhas que um Desenvolvedor C ++ pode encontrar. Isso pode tornar a programação de qualidade muito difícil e a manutenção muito cara. Aprender a sintaxe da linguagem e ter boas habilidades de programação em linguagens semelhantes, como C # e Java, não é suficiente para utilizar todo o potencial do C ++. Requer anos de experiência e grande disciplina para evitar erros em C ++. Neste artigo, vamos dar uma olhada em alguns dos erros comuns que são cometidos por desenvolvedores de todos os níveis se eles não forem cuidadosos o suficiente com o desenvolvimento em C ++.

Erro comum nº 1: usar pares “novo” e “excluir” incorretamente

Não importa o quanto tentemos, é muito difícil liberar toda a memória alocada dinamicamente. Mesmo que possamos fazer isso, muitas vezes não é seguro contra exceções. Vejamos um exemplo simples:



void SomeMethod() { ClassA *a = new ClassA; SomeOtherMethod(); // it can throw an exception delete a; }

Se uma exceção for lançada, o objeto “a” nunca será excluído. O exemplo a seguir mostra uma maneira mais segura e curta de fazer isso. Ele usa auto_ptr, que está obsoleto no C ++ 11, mas o padrão antigo ainda é amplamente usado. Ele pode ser substituído por C ++ 11 unique_ptr ou scoped_ptr do Boost, se possível.



void SomeMethod() { std::auto_ptr a(new ClassA); // deprecated, please check the text SomeOtherMethod(); // it can throw an exception }

Não importa o que aconteça, após a criação do objeto “a”, ele será excluído assim que a execução do programa sair do escopo.



No entanto, esse foi apenas o exemplo mais simples desse problema C ++. Existem muitos exemplos em que a exclusão deve ser feita em algum outro lugar, talvez em uma função externa ou outro thread. É por isso que o uso de new / delete em pares deve ser completamente evitado e, em vez disso, devem ser usados ​​smart pointers apropriados.

Erro comum nº 2: destruidor virtual esquecido

Este é um dos erros mais comuns que levam a vazamentos de memória dentro das classes derivadas se houver memória dinâmica alocada dentro delas. Existem alguns casos em que o destruidor virtual não é desejável, ou seja, quando uma classe não se destina à herança e seu tamanho e desempenho são cruciais. Destruidor virtual ou qualquer outra função virtual introduz dados adicionais dentro de uma estrutura de classe, ou seja, um ponteiro para uma tabela virtual que aumenta o tamanho de qualquer instância da classe.



No entanto, na maioria dos casos, as classes podem ser herdadas mesmo que não seja originalmente planejado. Portanto, é uma prática muito boa adicionar um destruidor virtual quando uma classe é declarada. Caso contrário, se uma classe não deve conter funções virtuais devido a razões de desempenho, é uma boa prática colocar um comentário dentro de um arquivo de declaração de classe indicando que a classe não deve ser herdada. Uma das melhores opções para evitar esse problema é usar um IDE que ofereça suporte à criação de destruidor virtual durante a criação de uma classe.

Um ponto adicional para o assunto são as classes / modelos da biblioteca padrão. Eles não se destinam a herança e não possuem um destruidor virtual. Se, por exemplo, criarmos uma nova classe de string aprimorada que herda publicamente de std :: string, haverá a possibilidade de alguém usá-la incorretamente com um ponteiro ou uma referência a std :: string e causar um vazamento de memória.



class MyString : public std::string { ~MyString() { // ... } }; int main() { std::string *s = new MyString(); delete s; // May not invoke the destructor defined in MyString }

Para evitar esses problemas de C ++, uma maneira mais segura de reutilizar uma classe / modelo da biblioteca padrão é usar herança ou composição privada.

Erro comum nº 3: excluir uma matriz com 'excluir' ou usar um ponteiro inteligente

Erro Comum # 3

gráfico de patrimônio líquido elon musk

A criação de matrizes temporárias de tamanho dinâmico geralmente é necessária. Depois que eles não forem mais necessários, é importante liberar a memória alocada. O grande problema aqui é que C ++ requer um operador de exclusão especial com colchetes [], que é esquecido facilmente. O operador delete [] não excluirá apenas a memória alocada para um array, mas primeiro chamará os destruidores de todos os objetos de um array. Também é incorreto usar o operador delete sem colchetes [] para tipos primitivos, embora não haja destruidor para esses tipos. Não há garantia para cada compilador de que um ponteiro para uma matriz apontará para o primeiro elemento da matriz, portanto, usar delete sem colchetes [] também pode resultar em comportamento indefinido.

Usar ponteiros inteligentes, como auto_ptr, unique_ptr, shared_ptr, com arrays também é incorreto. Quando esse ponteiro inteligente sai de um escopo, ele chama um operador de exclusão sem colchetes [], o que resulta nos mesmos problemas descritos acima. Se o uso de um ponteiro inteligente for necessário para um array, é possível usar scoped_array ou shared_array de Boost ou uma especialização unique_ptr.

Se a funcionalidade de contagem de referência não for necessária, o que é principalmente o caso de matrizes, a maneira mais elegante é usar vetores STL. Eles não se limitam a liberar memória, mas também oferecem funcionalidades adicionais.

Erro comum nº 4: retornando um objeto local por referência

Este é principalmente um erro de iniciante, mas vale a pena mencionar, uma vez que há muitos códigos legados que sofrem com esse problema. Vejamos o código a seguir, onde um programador queria fazer algum tipo de otimização, evitando cópias desnecessárias:

Complex& SumComplex(const Complex& a, const Complex& b) { Complex result; ….. return result; } Complex& sum = SumComplex(a, b);

O objeto “soma” agora apontará para o objeto local “resultado”. Mas onde está localizado o objeto “resultado” depois que a função SumComplex é executada? Lugar algum. Ele estava localizado na pilha, mas depois que a função retornou, a pilha foi desembrulhada e todos os objetos locais da função foram destruídos. Isso acabará resultando em um comportamento indefinido, mesmo para tipos primitivos. Para evitar problemas de desempenho, às vezes é possível usar a otimização do valor de retorno:

Complex SumComplex(const Complex& a, const Complex& b) { return Complex(a.real + b.real, a.imaginar + b.imaginar); } Complex sum = SumComplex(a, b);

Para a maioria dos compiladores de hoje, se uma linha de retorno contém um construtor de um objeto, o código será otimizado para evitar todas as cópias desnecessárias - o construtor será executado diretamente no objeto 'soma'.

Erro comum nº 5: usar uma referência a um recurso excluído

Esses problemas de C ++ acontecem com mais frequência do que você imagina e geralmente são vistos em aplicativos multithread. Vamos considerar o seguinte código:

Tópico 1:

Connection& connection= connections.GetConnection(connectionId); // ...

Tópico 2:

distribuição de probabilidade de simulação de monte carlo
connections.DeleteConnection(connectionId); // …

Tópico 1:

connection.send(data);

Neste exemplo, se ambos os threads usaram o mesmo ID de conexão, isso resultará em um comportamento indefinido. Os erros de violação de acesso geralmente são muito difíceis de encontrar.

Nestes casos, quando mais de um thread acessa o mesmo recurso, é muito arriscado manter ponteiros ou referências aos recursos, porque algum outro thread pode excluí-lo. É muito mais seguro usar ponteiros inteligentes com contagem de referência, por exemplo shared_ptr do Boost. Ele usa operações atômicas para aumentar / diminuir um contador de referência, portanto, é seguro para threads.

Erro comum nº 6: Permitir exceções para deixar destruidores

Freqüentemente, não é necessário lançar uma exceção de um destruidor. Mesmo assim, existe uma maneira melhor de fazer isso. No entanto, as exceções geralmente não são lançadas de destruidores explicitamente. Pode acontecer que um simples comando para registrar a destruição de um objeto cause o lançamento de uma exceção. Vamos considerar o seguinte código:

class A { public: A(){} ~A() { writeToLog(); // could cause an exception to be thrown } }; // … try { A a1; A a2; } catch (std::exception& e) { std::cout << 'exception caught'; }

No código acima, se a exceção ocorrer duas vezes, como durante a destruição de ambos os objetos, a instrução catch nunca é executada. Como há duas exceções em paralelo, não importa se são do mesmo tipo ou tipo diferente, o ambiente de tempo de execução C ++ não sabe como lidar com isso e chama uma função de término que resulta no término da execução de um programa.

Portanto, a regra geral é: nunca permita que exceções deixem destruidores. Mesmo que seja feio, a exceção potencial deve ser protegida assim:

try { writeToLog(); // could cause an exception to be thrown } catch (...) {}

Erro comum nº 7: usando “auto_ptr” (incorretamente)

O modelo auto_ptr foi descontinuado do C ++ 11 por vários motivos. Ainda é amplamente utilizado, uma vez que a maioria dos projetos ainda estão sendo desenvolvidos em C ++ 98. Ele tem uma certa característica que provavelmente não é familiar a todos os desenvolvedores de C ++ e pode causar sérios problemas para alguém que não se preocupe. Copiar o objeto auto_ptr irá transferir a propriedade de um objeto para outro. Por exemplo, o seguinte código:

auto_ptr a(new ClassA); // deprecated, please check the text auto_ptr b = a; a->SomeMethod(); // will result in access violation error

… Resultará em um erro de violação de acesso. Apenas o objeto “b” conterá um ponteiro para o objeto da Classe A, enquanto “a” estará vazio. Tentar acessar um membro da classe do objeto “a” resultará em um erro de violação de acesso. Existem muitas maneiras de usar auto_ptr incorretamente. Quatro coisas muito importantes a serem lembradas sobre eles são:

  1. Nunca use auto_ptr dentro de contêineres STL. A cópia de contêineres deixará os contêineres de origem com dados inválidos. Alguns algoritmos STL também podem levar à invalidação de “auto_ptr” s.

    razão para a crise da dívida grega
  2. Nunca use auto_ptr como um argumento de função, pois isso levará à cópia e deixe o valor passado para o argumento inválido após a chamada da função.

  3. Se auto_ptr for usado para membros de dados de uma classe, certifique-se de fazer uma cópia adequada dentro de um construtor de cópia e um operador de atribuição ou desabilite essas operações tornando-as privadas.

  4. Sempre que possível, use algum outro ponteiro inteligente moderno em vez de auto_ptr.

Erro comum nº 8: usando iteradores e referências inválidos

Seria possível escrever um livro inteiro sobre o assunto. Cada contêiner STL possui algumas condições específicas nas quais ele invalida iteradores e referências. É importante estar ciente desses detalhes ao usar qualquer operação. Assim como o problema anterior do C ++, este também pode ocorrer com muita frequência em ambientes multithread, portanto, é necessário usar mecanismos de sincronização para evitá-lo. Vamos ver o seguinte código sequencial como exemplo:

vector v; v.push_back(“string1”); string& s1 = v[0]; // assign a reference to the 1st element vector::iterator iter = v.begin(); // assign an iterator to the 1st element v.push_back(“string2”); cout << s1; // access to a reference of the 1st element cout << *iter; // access to an iterator of the 1st element

De um ponto de vista lógico, o código parece perfeitamente bom. No entanto, adicionar o segundo elemento ao vetor pode resultar na realocação da memória do vetor, o que tornará o iterador e a referência inválidos e resultará em um erro de violação de acesso ao tentar acessá-los nas últimas 2 linhas.

Erro comum nº 9: Passar um objeto por valor

Erro Comum # 9

Você provavelmente sabe que é uma má ideia passar objetos por valor devido ao seu impacto no desempenho. Muitos deixam assim para evitar a digitação de caracteres extras, ou provavelmente pensam em retornar mais tarde para fazer a otimização. Normalmente, isso nunca é feito e, como resultado, leva a um código de menor desempenho e um código que está sujeito a um comportamento inesperado:

class A { public: virtual std::string GetName() const {return 'A';} … }; class B: public A { public: virtual std::string GetName() const {return 'B';} ... }; void func1(A a) { std::string name = a.GetName(); ... } B b; func1(b);

Este código será compilado. Chamar a função 'func1' criará uma cópia parcial do objeto 'b', ou seja, copiará apenas a parte da classe 'A' do objeto 'b' para o objeto 'a' ('problema de corte'). Portanto, dentro da função, ele também chamará um método da classe “A” ao invés de um método da classe “B” que provavelmente não é o que é esperado por alguém que chama a função.

Problemas semelhantes ocorrem ao tentar capturar exceções. Por exemplo:

class ExceptionA: public std::exception; class ExceptionB: public ExceptionA; try { func2(); // can throw an ExceptionB exception } catch (ExceptionA ex) { writeToLog(ex.GetDescription()); throw; }

Quando uma exceção do tipo ExceptionB é lançada a partir da função 'func2', ela será capturada pelo bloco catch, mas devido ao problema de fatiamento, apenas uma parte da classe ExceptionA será copiada, o método incorreto será chamado e também será lançado novamente lançará uma exceção incorreta para um bloco try-catch externo.

Para resumir, sempre passe os objetos por referência, não por valor.

Erro comum nº 10: usar conversões definidas pelo usuário por construtor e operadores de conversão

Até mesmo as conversões definidas pelo usuário são muito úteis às vezes, mas podem levar a conversões imprevistas que são muito difíceis de localizar. Digamos que alguém criou uma biblioteca que tem uma classe string:

class String { public: String(int n); String(const char *s); …. }

O primeiro método destina-se a criar uma string de comprimento n, e o segundo, a criar uma string contendo os caracteres fornecidos. Mas o problema começa assim que você tem algo assim:

é um llc ac corp
String s1 = 123; String s2 = ‘abc’;

No exemplo acima, s1 se tornará uma string de tamanho 123, não uma string que contém os caracteres “123”. O segundo exemplo contém aspas simples em vez de aspas duplas (o que pode acontecer por acidente), o que também resultará na chamada do primeiro construtor e na criação de uma string com um tamanho muito grande. Esses são exemplos realmente simples e há muitos casos mais complicados que levam a confusão e conversões imprevistas que são muito difíceis de encontrar. Existem 2 regras gerais de como evitar esses problemas:

  1. Defina um construtor com palavra-chave explícita para proibir conversões implícitas.

  2. Em vez de usar operadores de conversão, use métodos de conversação explícitos. Requer um pouco mais de digitação, mas é muito mais limpo de ler e pode ajudar a evitar resultados imprevisíveis.

Conclusão

C ++ é uma linguagem poderosa. Na verdade, muitos dos aplicativos que você usa todos os dias em seu computador e que aprendeu a adorar são provavelmente desenvolvidos em C ++. Como linguagem, C ++ oferece uma tremenda quantidade de flexibilidade para o desenvolvedor, por meio de alguns dos recursos mais sofisticados vistos em linguagens de programação orientadas a objetos. No entanto, esses recursos sofisticados ou flexibilidades podem muitas vezes se tornar a causa de confusão e frustração para muitos desenvolvedores se não forem usados ​​com responsabilidade. Esperançosamente, esta lista o ajudará a entender como alguns desses erros comuns influenciam o que você pode alcançar com C ++.

Relacionado: Como aprender as linguagens C e C ++: a lista definitiva

Quando o Congresso fez política sobre a 'vaca sagrada'

Pesquisar

Quando o Congresso fez política sobre a 'vaca sagrada'
Drones comerciais estão revolucionando as operações de negócios

Drones comerciais estão revolucionando as operações de negócios

Processos Financeiros

Publicações Populares
Quem, o quê e por quê - um guia para métodos de teste do usuário
Quem, o quê e por quê - um guia para métodos de teste do usuário
Aflição financeira em uma crise: você não pode prever, você pode se preparar
Aflição financeira em uma crise: você não pode prever, você pode se preparar
Bangladesh condena seis militantes à morte por matar dois ativistas gays
Bangladesh condena seis militantes à morte por matar dois ativistas gays
Mães solteiras na arte de criar filhos sozinhas
Mães solteiras na arte de criar filhos sozinhas
Vender uma empresa para valor máximo em um mercado desafiador de fusões e aquisições
Vender uma empresa para valor máximo em um mercado desafiador de fusões e aquisições
 
Robo-conselheiro Risco de portfólio da indústria: eficiência ou redução de cantos?
Robo-conselheiro Risco de portfólio da indústria: eficiência ou redução de cantos?
EUA: corrida para prefeito de Honolulu segue para segundo turno
EUA: corrida para prefeito de Honolulu segue para segundo turno
13 podcasts que todo designer deve ouvir
13 podcasts que todo designer deve ouvir
Vazamentos de Panama Papers podem dar a Sanders algum poder de fogo contra o rival Clinton
Vazamentos de Panama Papers podem dar a Sanders algum poder de fogo contra o rival Clinton
Com 21 anos e raízes de Kerala, é o mais jovem na lista de MBE do Queen
Com 21 anos e raízes de Kerala, é o mais jovem na lista de MBE do Queen
Publicações Populares
  • devo aprender c ou c ++ primeiro
  • designers de sites começam a desenvolver a arquitetura de informação para um site por
  • transformar meu rasberry pi em um servidor web
  • incluindo arquivos de cabeçalho c ++
  • tutorial do angular 5 para iniciantes passo a passo
  • servidor linux raspberry pi 3
Categorias
  • Noticias Do Mundo
  • Lucratividade E Eficiência
  • Ágil
  • Família
  • © 2022 | Todos Os Direitos Reservados

    portaldacalheta.pt