Depois de ler esse famoso discurso de Linus Torvalds , perguntei-me quais são as armadilhas para os programadores em C ++. Não estou explicitamente me referindo a erros de digitação ou fluxo incorreto de programa, conforme tratado nesta pergunta e suas respostas , mas a erros de mais alto nível que não são detectados pelo compilador e não resultam em erros óbvios na primeira execução, erros de design completos, coisas improváveis em C, mas que provavelmente serão feitas em C ++ por recém-chegados que não entendem todas as implicações de seu código.
Também sou bem-vindo a respostas que apontam para uma enorme queda no desempenho, onde normalmente não seria esperado. Um exemplo do que um dos meus professores me contou uma vez sobre um gerador de analisador LR (1) que escrevi:
Você usou muitas instâncias de herança e virtualidade desnecessárias. A herança torna um design muito mais complicado (e ineficiente por causa do subsistema RTTI (inércia do tipo de tempo de execução)) e, portanto, deve ser usado apenas onde faz sentido, por exemplo, para as ações na tabela de análise. Como você faz uso intensivo de modelos, praticamente não precisa de herança. "
virtual
funções, certo?dynamic_cast
deve ser bem-sucedido ou não, e poucas outras coisas, mas a reflexão abrange muito mais, incluindo a possibilidade de recuperar informações sobre atributos ou funções de membros, o que não é presente em C ++.Respostas:
Torvalds está falando demais aqui.
OK, por que ele está falando sério:
Primeiro de tudo, seu discurso retórico não é realmente nada, mas discurso retórico. Há muito pouco conteúdo real aqui. A única razão pela qual é realmente famoso ou até levemente respeitado é porque foi feita pelo Deus Linux. Seu principal argumento é que C ++ é uma porcaria e ele gosta de irritar as pessoas em C ++. É claro que não há razão alguma para responder a isso e qualquer um que considere um argumento razoável está além da conversa de qualquer maneira.
Quanto ao que pode ser considerado como seus pontos mais objetivos:
Basicamente, Torvalds está falando maluco. Não há argumento inteligível sobre nada. Esperar uma refutação séria de tal absurdo é simplesmente bobagem. Estou me dizendo para "expandir" em uma refutação de algo que eu deveria expandir se fosse onde eu o dissesse. Se você realmente, honestamente, olhar para o que Torvalds disse, verá que ele não disse nada.
Só porque Deus diz que não significa que faça algum sentido ou deva ser levado mais a sério do que se algum bozo aleatório o dissesse. Verdade seja dita, Deus é apenas mais um bozo aleatório.
Respondendo à pergunta real:
Provavelmente, a pior e mais comum prática ruim de C ++ é tratá-la como C. O uso contínuo de funções da API C como printf, gets (também considerado ruim em C), strtok, etc ... não apenas falha em aproveitar a energia fornecida pelo sistema mais rígido, eles inevitavelmente levam a complicações adicionais ao tentar interagir com o código C ++ "real". Então, basicamente, faça exatamente o oposto do que Torvalds está aconselhando.
Aprenda a alavancar o STL e o Boost para obter mais detecção de erros no tempo de compilação e facilitar sua vida de outras maneiras gerais (o tokenizer de impulso, por exemplo, é seguro para o tipo E uma interface melhor). É verdade que você precisará aprender a ler erros de modelo, o que é assustador no começo, mas (na minha experiência de qualquer maneira) é francamente muito mais fácil do que tentar depurar algo que gera comportamento indefinido durante o tempo de execução, que a API C faz bastante fácil de fazer.
Para não dizer que C não é tão bom. É claro que eu gosto mais de C ++. Programadores C como C melhor. Existem trocas e gostos subjetivos em jogo. Também há muita desinformação e FUD flutuando. Eu diria que há mais FUD e desinformação flutuando sobre o C ++, mas sou tendenciosa a esse respeito. Por exemplo, os problemas de "inchaço" e "desempenho" que o C ++ supostamente não tem, na verdade, são problemas importantes na maioria das vezes e certamente são exagerados nas proporções da realidade.
Quanto aos problemas aos quais seu professor está se referindo, eles não são exclusivos do C ++. No OOP (e na programação genérica), você deseja preferir a composição do que a herança. A herança é o relacionamento de acoplamento mais forte possível que existe em todos os idiomas OO. C ++ adiciona mais um que é mais forte, a amizade. A herança polimórfica deve ser usada para representar abstrações e relacionamentos "é-a", nunca deve ser usada para reutilização. Esse é o segundo maior erro que você pode cometer em C ++, e é bem grande, mas está longe de ser exclusivo da linguagem. Você também pode criar relacionamentos de herança excessivamente complexos em C # ou Java, e eles terão exatamente os mesmos problemas.
fonte
Eu sempre pensei que os perigos do C ++ eram altamente exagerados por inexperientes, C com programadores de classes.
Sim, o C ++ é mais difícil de entender do que algo como Java, mas se você programar usando técnicas modernas, é muito fácil escrever programas robustos. Eu honestamente não têm que muito mais difícil de um tempo de programação em C ++ do que eu em linguagens como Java, e muitas vezes eu encontro-me faltando certas abstrações C ++ como modelos e RAII quando eu projeto em outros idiomas.
Dito isto, mesmo depois de anos de programação em C ++, de vez em quando cometerei um erro realmente estúpido que não seria possível em uma linguagem de nível superior. Uma armadilha comum no C ++ é ignorar a vida útil do objeto: em Java e C #, geralmente você não precisa se preocupar com a vida útil do objeto *, porque todos os objetos existem na pilha e são gerenciados por um coletor de lixo mágico.
Agora, no C ++ moderno, geralmente você também não precisa se preocupar muito com a vida útil do objeto. Você tem destruidores e ponteiros inteligentes que gerenciam a vida útil dos objetos para você. 99% do tempo, isso funciona maravilhosamente. Mas, de vez em quando, você é ferrado por um ponteiro oscilante (ou referência). Por exemplo, recentemente, eu tive um objeto (vamos chamá-lo
Foo
) que continha uma variável de referência interna para outro objeto (vamos chamá-loBar
). Em um ponto, eu estupidamente organizei as coisas para queBar
estivessem fora de escopo antesFoo
, masFoo
o destruidor acabou chamando uma função de membro deBar
. Escusado será dizer que as coisas não saíram bem.Agora, não posso culpar C ++ por isso. Era o meu próprio design ruim, mas a questão é que esse tipo de coisa não aconteceria em uma linguagem gerenciada de nível superior. Mesmo com indicadores inteligentes e similares, às vezes você ainda precisa ter consciência do tempo de vida do objeto.
* Se o recurso que está sendo gerenciado é memória, é isso.
fonte
A diferença no código geralmente está mais relacionada ao programador do que ao idioma. Em particular, um bom programador de C ++ e um programador de C virão para soluções igualmente boas (mesmo que diferentes). Agora, C é uma linguagem mais simples (como uma linguagem) e isso significa que há menos abstrações e mais visibilidade do que o código realmente faz.
Uma parte de suas reclamações (ele é conhecido por suas reclamações contra C ++) é baseada no fato de que mais pessoas adotam C ++ e escrevem código sem realmente entender o que algumas das abstrações ocultam e fazem suposições erradas.
fonte
std::vector<bool>
alteração de cada valor?for ( std::vector<bool>::iterator it = v.begin(), end = v.end(); it != end; ++it ) { *it = !*it; }
? O que é abstraído*it = !*it;
?std::vector<bool>
é um erro bem conhecido, mas é realmente um bom exemplo do que está sendo discutido: abstrações são boas, mas é preciso ter cuidado com o que elas ocultam. O mesmo pode e acontecerá no código do usuário. Para iniciantes, eu vi pessoas em C ++ e Java usando exceções para executar o controle de fluxo e código que se parece com uma chamada de função de aninhamento que é realmente um lançador de exceção de resgate:void endOperation();
implementado comothrow EndOperation;
. Um bom programador evitará essas construções surpreendentes , mas o fato é que você pode encontrá-las.Uso excessivo de
try/catch
blocos.Isso geralmente decorre de linguagens como Java e as pessoas argumentam que o C ++ não possui uma
finalize
cláusula.Mas esse código exibe dois problemas:
file
antes do arquivotry/catch
, porque você não pode realmenteclose
um arquivo que não existecatch
. Isso leva a um "vazamento de escopo"file
visível após o fechamento. Você pode adicionar um bloco, mas ...: /return
no meio dotry
escopo, o arquivo não será fechado (é por isso que as pessoas reclamam da falta definalize
cláusula)No entanto, em C ++, temos maneiras muito mais eficientes de lidar com esse problema que:
finalize
using
defer
Temos o RAII, cuja propriedade realmente interessante é melhor resumida como
SBRM
(Gerenciamento de recursos limitados no escopo).Ao criar a classe para que seu destruidor limpe os recursos que possui, não colocamos o ônus de gerenciar o recurso em todos e cada usuário!
Esse é o recurso que sinto falta em qualquer outro idioma, e provavelmente o mais esquecido.
A verdade é que raramente é necessário escrever um
try/catch
bloco em C ++, além do nível superior, para evitar o término sem o log.fonte
fopen
efclose
aqui.) RAII é a maneira "correta" de fazer as coisas aqui, mas é inconveniente para as pessoas que querem usar bibliotecas C do C ++ .File file("some.txt");
e é isso (nãoopen
, nãoclose
, nãotry
...)Um erro comum que se encaixa nos seus critérios é não entender como os construtores de cópias funcionam ao lidar com a memória alocada em sua classe. Eu perdi a conta da quantidade de tempo que gastei consertando falhas ou vazamentos de memória porque um 'noob' colocou seus objetos em um mapa ou vetor e não escreveu construtores e destruidores de cópia adequadamente.
Infelizmente, o C ++ está cheio de truques 'ocultos' como esse. Mas reclamar é como reclamar que você foi à França e não conseguiu entender o que as pessoas estavam dizendo. Se você for para lá, aprenda o idioma.
fonte
O C ++ permite uma grande variedade de recursos e estilos de programação, mas isso não significa que essas são realmente boas maneiras de o C ++ ser usado. E, de fato, é incrivelmente fácil usar o C ++ incorretamente.
Ele precisa ser aprendido e entendido adequadamente , apenas o aprendizado (ou o uso como se fosse usar outra linguagem) levará a códigos ineficientes e propensos a erros.
fonte
Bem ... Para começar, você pode ler o C ++ FAQ Lite
Então, várias pessoas criaram carreiras escrevendo livros sobre os meandros do C ++:
Herb Sutter e Scott Meyers, a saber.
Quanto ao discurso retórico de Torvalds, falta substância ... vamos às pessoas, sério: nenhuma outra língua lá fora teve tanta tinta derramada ao lidar com as nuances da língua. Seus livros em Python, Ruby e Java se concentram em aplicativos de escrita ... seus livros em C ++ se concentram em recursos / dicas / armadilhas de linguagem boba.
fonte
A modelagem muito pesada pode não resultar em erros no início. Com o passar do tempo, porém, as pessoas precisarão modificar esse código e terão dificuldade em entender um modelo enorme. É aí que os bugs entram - o mal-entendido causa comentários "Compila e executa", que geralmente levam a um código quase-mas-não-muito-correto.
Geralmente, se me vejo criando um modelo genérico profundo de três níveis, paro e penso em como ele poderia ser reduzido a um. Geralmente, o problema é resolvido extraindo funções ou classes.
fonte
Aviso: isso não é tanto uma resposta quanto uma crítica da conversa à qual o "usuário desconhecido" vinculou em sua resposta.
Seu primeiro ponto principal é o (supostamente) "padrão sempre em mudança". Na realidade, os exemplos que ele fornece relacionam-se a alterações no C ++ antes de haver um padrão. Desde 1998 (quando o primeiro padrão C ++ foi finalizado), as alterações na linguagem foram mínimas - na verdade, muitos argumentariam que o problema real é que mais alterações deveriam ser feitas. Estou razoavelmente certo de que todo o código que está em conformidade com o padrão C ++ original ainda está em conformidade com o padrão atual. Embora seja um pouco menos certo, a menos que algo mude rapidamente (e inesperadamente), o mesmo também será verdadeiro com o próximo padrão C ++ (teoricamente, todo o código usado
export
vai quebrar, mas praticamente não existe; do ponto de vista prático, não é um problema). Posso pensar em algumas outras linguagens, sistemas operacionais (ou muitas outras coisas relacionadas ao computador) que podem fazer tal afirmação.Ele então entra em "estilos sempre em mudança". Mais uma vez, a maioria de seus argumentos é quase absurda. Ele tenta caracterizar
for (int i=0; i<n;i++)
como "velho e preso" efor (int i(0); i!=n;++i)
"nova gostosura". A realidade é que, embora existam tipos para os quais essas mudanças possam fazer sentido,int
isso não faz diferença - e mesmo quando você pode obter algo, raramente é necessário escrever código bom ou correto. Mesmo na melhor das hipóteses, ele está fazendo uma montanha fora de uma montanha.Sua próxima alegação é que o C ++ está "otimizando na direção errada" - especificamente, embora ele admita que o uso de boas bibliotecas seja fácil, ele afirma que o C ++ "torna quase impossível a escrita de boas bibliotecas". Aqui, acredito, é um dos seus erros mais fundamentais. Na realidade, escrever boas bibliotecas para quase qualquer idioma é extremamente difícil. No mínimo, escrever uma boa biblioteca requer a compreensão de algum domínio com problemas tão bem que seu código funciona para uma infinidade de aplicativos possíveis (ou relacionados a) nesse domínio. A maior parte do que o C ++ realmente faz é "elevar a fasquia" - depois de ver o quão melhor uma biblioteca pode ser, as pessoas raramente estão dispostas a voltar a escrever o tipo de coisa que teriam de outra forma.realmente bons codificadores escrevem algumas bibliotecas, que podem ser usadas (facilmente, como ele admite) pelo "resto de nós". Este é realmente um caso em que "isso não é um bug, é um recurso".
Não tentarei acertar todos os pontos na ordem (isso levaria páginas), mas pular diretamente para o ponto de fechamento. Ele cita Bjarne dizendo: "a otimização completa do programa pode ser usada para eliminar tabelas de funções virtuais não utilizadas e dados RTTI. Essa análise é particularmente adequada para programas relativamente pequenos que não usam vínculo dinâmico".
Ele critica isso fazendo uma afirmação sem suporte de que "Este é um problema realmente difícil", chegando até a compará-lo ao problema de parada. Na realidade, não é nada disso - de fato, o vinculador incluído no Zortech C ++ (praticamente o primeiro compilador C ++ para MS-DOS, nos anos 80) fez isso. É verdade que é difícil ter certeza de que toda parte de dados possivelmente estranhos foi eliminada, mas ainda é inteiramente razoável fazer um trabalho bastante justo.
Independentemente disso, porém, o ponto muito mais importante é que isso é totalmente irrelevante para a maioria dos programadores em qualquer caso. Como todos nós que desmontamos um pouco de código sabem, a menos que você escreva a linguagem assembly sem nenhuma biblioteca, seus executáveis quase certamente contêm uma quantidade razoável de "coisas" (código e dados, em casos típicos) que você provavelmente nem sabe, para não mencionar que realmente está usando. Para a maioria das pessoas, na maioria das vezes, isso simplesmente não importa - a menos que você esteja desenvolvendo para os menores sistemas embarcados, esse consumo extra de armazenamento é simplesmente irrelevante.
No final, é verdade que esse discurso retórico tem um pouco mais de substância do que a idiotice de Linus - mas isso está dando a ela exatamente o maldito elogio que merece.
fonte
Como programador em C que teve que codificar em C ++ devido a circunstâncias inevitáveis, aqui está minha experiência. Há muito poucas coisas que uso em C ++ e, principalmente, em C. A principal razão é porque eu não entendo C ++ tão bem. Eu não tinha / não tinha um mentor para me mostrar os meandros do C ++ e como escrever um bom código nele. E sem a orientação de um código C ++ muito bom, é extremamente difícil escrever um código bom em C ++. IMHO, essa é a maior desvantagem do C ++, porque é difícil encontrar bons codificadores de C ++ dispostos a segurar iniciantes.
Alguns dos hits de desempenho que eu vi geralmente são devidos à alocação de memória mágica do STL (sim, você pode alterar o alocador, mas quem faz isso quando ele inicia o C ++?). Você costuma ouvir argumentos de especialistas em C ++ de que vetores e matrizes oferecem desempenho semelhante, porque os vetores usam matrizes internamente e a abstração é super eficiente. Eu descobri que isso é verdade na prática para acessar vetores e modificar valores existentes. Mas não é verdade para adicionar uma nova entrada, construção e destruição de vetores. O gprof mostrou que cumulativamente 25% do tempo para um aplicativo era gasto em construtores de vetores, destruidores, memmove (para realocação de vetores inteiros para adicionar novo elemento) e outros operadores de vetores sobrecarregados (como ++).
Na mesma aplicação, o vetor de somethingSmall foi usado para representar um somethingBig. Não havia necessidade de acesso aleatório de algo pequeno em algo grande. Ainda um vetor foi usado em vez de uma lista. A razão pela qual o vetor foi usado? Como o codificador original estava familiarizado com a matriz como sintaxe de vetores e não muito familiarizado com os iteradores necessários para as listas (sim, ele é do fundo C). Continua a provar que é necessária muita orientação de especialistas para acertar o C ++. O C oferece tão poucas construções básicas com absolutamente nenhuma abstração, que você pode acertar muito mais facilmente do que o C ++.
fonte
Embora eu goste de Linus Thorvalds, este discurso é sem substância - apenas um discurso.
Se você gosta de ver um discurso retórico, aqui está um: "Por que o C ++ faz mal ao meio ambiente, causa aquecimento global e mata filhotes" http://chaosradio.ccc.de/camp2007_m4v_1951.html Material adicional: http: // www .fefe.de / c ++ /
Uma conversa divertida, imho
fonte
STL e boost são portáteis, no nível do código-fonte. Eu acho que Linus está falando é que o C ++ não possui uma ABI (interface binária de aplicativo). Portanto, você precisa compilar todas as bibliotecas vinculadas, com a mesma versão do compilador e com as mesmas opções, ou então limitar-se à C ABI nos limites da dll. Também acho que isso é possível .. mas, a menos que você esteja criando bibliotecas de terceiros, poderá controlar o ambiente de construção. Acho que me restringir ao C ABI não vale a pena. A conveniência de poder transmitir seqüências de caracteres, vetores e ponteiros inteligentes de uma dll para outra vale a pena ter que reconstruir todas as bibliotecas ao atualizar os compiladores ou alterar as opções do compilador. As regras de ouro que sigo são:
- Herdar para reutilizar a interface, não a implementação
-Preferir agregação sobre herança
-Preferir, sempre que possível, funções livres para métodos membros
Sempre use o idioma RAII para tornar seu código altamente seguro. Evite tentar pegar.
-Use ponteiros inteligentes, evite ponteiros nus (sem dono)
-Preferir a semântica do valor para referenciar a semântica
-Não reinvente a roda, use stl e aumente
-Use o idioma Pimpl para ocultar privado e / ou fornecer um firewall de compilador
fonte
Não colocando uma final
;
no final de uma declaração clase, pelo menos em algumas versões do VC.fonte