Acredito que misturei códigos C e C ++ quando não deveria; Isso é um problema e como corrigir?

10

Histórico / Cenário

Comecei a escrever um aplicativo CLI puramente em C (meu primeiro programa C ou C ++ adequado que não era "Hello World" ou uma variação dele). No meio do caminho, eu estava trabalhando com "strings" de entrada do usuário (matrizes de caracteres) e descobri o objeto streamer de strings C ++. Vi que podia salvar código usando esses, então usei-os através do aplicativo. Isso significa que alterei a extensão do arquivo para .cpp e agora compile o aplicativo em g++vez de gcc. Portanto, com base nisso, eu diria que o aplicativo agora é tecnicamente um aplicativo C ++ (embora mais de 90% do código esteja escrito no que eu chamaria de C, pois há muito cruzamento entre os dois idiomas, dada minha experiência limitada em os dois). É um único arquivo .cpp com cerca de 900 linhas.

Fatores Importantes

Quero que o programa seja gratuito (como em dinheiro), livremente distribuível e utilizável para todos. Minha preocupação é que alguém olhe o código e pense algo com o efeito de:

Oh, olhe a codificação, é horrível, este programa não pode me ajudar

Quando potencialmente poderia! Outra questão é o código ser eficiente (é um programa para testar a conectividade Ethernet). Não deve haver partes do código que sejam tão ineficientes que possam prejudicar gravemente o desempenho do aplicativo ou sua saída. No entanto, acho que essa é uma pergunta para o Stack Overflow ao pedir ajuda com funções, métodos, chamadas de objetos, etc.

Minha pergunta

Tendo (na minha opinião) misturado C e C ++ onde talvez eu não devesse. Devo procurar reescrever tudo em C ++ (com isso, quero dizer implementar mais objetos e métodos C ++, onde talvez eu tenha codificado algo em um estilo C que possa ser condensado usando técnicas mais recentes de C ++) ou remover o uso de objetos de streamer de string e trazer tudo "de volta" ao código C? Existe uma abordagem correta aqui? Estou perdido e preciso de algumas orientações sobre como manter esta aplicação "Bom" aos olhos das massas, para que elas a usem e se beneficiem.

O Código - Atualização

Aqui está um link para o código. São cerca de 40% dos comentários, comento quase todas as linhas até me sentir mais fluente. Na cópia a que vinculei, removi praticamente todos os comentários. Espero que isso não dificulte a leitura. Espero, porém, que ninguém precise compreendê-lo completamente. Se eu cometi falhas fatais de design, espero que sejam identificáveis ​​facilmente. Devo também mencionar, estou escrevendo alguns desktops e laptops Ubuntu. Não pretendo portar o código para outros sistemas operacionais.

jwbensley
fonte
3
Desambiguei a CLI para você. A CLI também pode se referir à Common Language Infrastructure, que não faz muito sentido da perspectiva C.
9788 Robert
Você pode reescrevê-lo no FORTRAN. Nunca ouvi falar em OOF.
ott--
2
Eu não me preocuparia muito com as pessoas que não usam seu software se não for "bonito". 99% dos usuários nem olham para o código ou se preocupam com a forma como ele é escrito, desde que cumpra o que eles precisam fazer. É, no entanto, importante que o código seja consistente etc. para ajudá- lo a mantê-lo a longo prazo.
Evicatos
11
Por que você não cria seu software livre (por exemplo, sob licença GPLv3 ), com o código, por exemplo, no github . Seu código está sem um LICENSEarquivo. Você pode obter um feedback interessante.
Basile Starynkevitch

Respostas:

13

Vamos começar do começo: códigos C e C ++ mistos são bastante comuns. Então você está em um grande clube para começar. Temos enormes bases de código C na natureza. Mas, por razões óbvias, muitos programadores se recusam a escrever pelo menos coisas novas em C, tendo acesso ao C ++ no mesmo compilador, novos módulos começam a ser escritos dessa maneira - a princípio, deixando apenas as partes existentes.

Eventualmente, alguns arquivos existentes são recompilados como C ++ e algumas pontes podem ser excluídas ... Mas isso pode levar muito tempo.

Você está um pouco à frente, seu sistema completo agora é C ++, apenas a maior parte está escrita em "estilo C". E você vê um mix de estilos um problema, o que você não deveria: C ++ é uma linguagem de paradigmas múltiplos que suporta muitos estilos e permite que eles coexistam para sempre. Na verdade, essa é a força principal, que você não é forçado a um estilo único. Um que seria abaixo do ideal aqui e ali, com alguma sorte, não em todo lugar.

Retrabalhar a base de código é uma boa ideia, se estiver quebrada. Ou se estiver no caminho do desenvolvimento. Mas se funcionar (no sentido original da palavra), siga o princípio mais básico de engenharia: se não estiver quebrado, não conserte. Deixe as peças frias em paz, coloque seu esforço onde é importante. Nas peças que são ruins, perigosas - ou em novos recursos, basta refatorar as peças para torná-las uma cama.

Se você procura informações gerais, aqui está o que vale a pena despejar de uma base de código C:

  • todas as funções str * e char [] - substitua-as por uma classe de string
  • se você usar sprintf, crie uma versão que retorne uma sequência com o resultado ou a coloque na sequência e substitua o uso. (Se você nunca se incomodou com fluxos, faça um favor a si mesmo e apenas ignore-os, a menos que você goste; o gcc fornece segurança de tipo perfeita e pronta para verificar formatos, basta adicionar o atributo apropriado.
  • mais malloc e grátis - NÃO com novos e excluir, mas com vetor, lista, mapa e outros itens.
  • o restante do gerenciamento de memória (após os dois pontos anteriores, deve ser bem raro, cubra com ponteiros inteligentes ou implemente suas coleções especiais
  • substitua todos os outros usos de recursos (FILE *, mutex, lock, etc) para usar wrappers ou classes RAII

Quando você termina, aproxima-se do ponto em que a base de código pode ser razoavelmente segura para exceções, para que você possa descartar o código de retorno usando exceções e rara tentativa / captura apenas em funções de alto nível.

Além disso, basta escrever um novo código em algum C ++ íntegro e, se nascerem algumas classes que substituem o código existente, escolha-as.

Eu não mencionei coisas relacionadas à sintaxe, obviamente use refs em vez de ponteiros em todo o novo código, mas substituir peças C antigas apenas para essa alteração não é um bom valor. Transmissões que você deve resolver, eliminar tudo o que puder e usar variantes do C ++ nas funções de wrapper para o restante. E muito importante, adicione const sempre que aplicável. Estes intercalam com as balas anteriores. E consolide suas macros e substitua o que você pode transformar em enum, função embutida ou modelo.

Sugiro ler os Padrões de Codificação C ++ de Sutter / Alexandrescu, se ainda não tiverem sido executados, e segui-los de perto.

Balog Pal
fonte
Muito obrigado pela entrada Balog, todos os bons conselhos aos meus olhos e eu concordo com tudo isso. Vou procurar mudar lentamente o código seção por seção, eu acho, priorizando o código de trabalho.
jwbensley
7

Resumindo: você não está indo para o inferno, nada de ruim vai acontecer, mas também não vai ganhar nenhuma competição de beleza.

Embora seja perfeitamente possível usar o C ++ como um "C melhor", você está jogando fora muitos dos benefícios do C ++, mas como não está se restringindo à baunilha C, não está obtendo nenhum dos benefícios do C (simplicidade, portabilidade , transparência, interoperabilidade). Em outras palavras: C ++ sacrifica algumas das qualidades de C para obter outras - por exemplo, a transparência de C, onde você sempre pode ver claramente onde e quando ocorrem as alocações de memória, é negociado pelas abstrações mais poderosas de C ++.

Como seu código parece funcionar bem agora, provavelmente não é uma boa idéia reescrevê-lo apenas para mantê-lo: mantenha-o como está agora e altere-o para C ++ mais idiomático conforme você for, um pedaço de cada vez, sempre que você trabalha em qualquer parte. E lembre-se das lições aprendidas assim para o seu próximo projeto, principalmente este: C e C ++ não são a mesma linguagem, e é melhor fazer uma escolha antecipada do que decidir mudar para C ++ na metade do seu projeto .

tdammers
fonte
Obrigado tdammers pelo conselho. Concordo com o que você está dizendo e estou aceitando, obrigado!
jwbensley
4

C ++ é uma linguagem muito complexa, no sentido de que possui muitos recursos. É também uma linguagem que suporta múltiplos paradigmas, o que significa que permite escrever código inteiramente procedural sem usar nenhum objeto.

Portanto, como o C ++ é tão complexo, você geralmente vê as pessoas usarem um subconjunto limitado de seus recursos em seus códigos. Os iniciantes geralmente usam apenas a E / S do fluxo, os objetos de sequência e new / delete em vez de malloc / free, e talvez referências em vez de ponteiros. À medida que aprende sobre os recursos orientados a objetos, você pode começar a escrever em um estilo chamado "C com classes". Eventualmente, à medida que você aprende mais sobre C ++, você começa a usar modelos, RAII , STL , ponteiros inteligentes etc.

O que estou tentando enfatizar é que o aprendizado de C ++ é um processo que leva tempo. Sim, agora seu código provavelmente parece ter sido escrito por um programador C tentando escrever C ++. E já que você está apenas aprendendo, tudo bem. No entanto, isso me faz estremecer quando vejo esse tipo de coisa no código de produção escrito por programadores experientes que deveriam conhecer melhor.

Lembre-se, um bom programador Fortran pode escrever um bom código Fortran em qualquer idioma. :)

Dima
fonte
2

Parece que você está recapitulando exatamente o caminho que muitos programadores antigos de C seguiram ao acessar o C ++. Você está usando como um "C melhor". Vinte anos atrás, isso fazia algum sentido, mas neste momento há tantas construções mais poderosas em C ++ (como a Standard Template Library) que raramente faz sentido agora. No mínimo, você provavelmente deve fazer o seguinte para evitar aneurismas de programadores em C ++ ao analisar seu código:

  • Use fluxos em vez de? printffamília.
  • Use em newvez de malloc.
  • Use as classes de contêiner STL para todas as estruturas de dados.
  • Use em std::stringvez de char*sempre que possível
  • Entenda e use RAII

Se o seu programa for curto (e ~ 900 linhas parecerem curtas), pessoalmente não acho que seja necessário ou útil a tentativa de criar um conjunto robusto de classes.

Gort the Robot
fonte
Obrigado pelo conselho Steven, Sim, eu tive a mesma idéia sobre classes e acho que posso organizar o código em um único arquivo. Obrigado!
jwbensley
2

A resposta aceita menciona apenas os profissionais da conversão de C em C ++ idiomático, como se o código C ++ fosse, em algum sentido absoluto, melhor que o código C. Concordo com outras respostas que provavelmente não é necessário fazer alterações drásticas no código misto se não estiver quebrado.

Se a mistura de C e C ++ é sustentável depende de como a mistura é feita. Erros sutis podem ser introduzidos, por exemplo, quando exceções são geradas no código C (o que não é seguro), causando vazamentos de memória ou corrupção de dados. Uma maneira mais segura e bastante comum é agrupar bibliotecas C ou interfaces em classes ou usá-las em algum outro tipo de isolamento em um projeto C ++.

Antes de decidir reescrever todo o seu projeto em C ++ idiomático (ou C), você deve estar ciente de que muitas das alterações apresentadas em outras respostas podem tornar seu programa mais lento ou causar outros efeitos indesejados. Por exemplo:

  • alterar as strings C alocadas à pilha para std :: strings pode causar alocações de heap desnecessárias
  • alterar ponteiros brutos para alguns tipos de ponteiros compartilhados (como std :: shared_ptr) causa sobrecarga de acesso devido à contagem de referências, funções internas de membros virtuais e segurança de threads
  • fluxos de biblioteca padrão são mais lentos que os equivalentes C
  • o uso descuidado de classes RAII, como contêineres, pode causar operações desnecessárias, especialmente quando a semântica de movimentação do C ++ 11 não pode ser usada
  • modelos podem causar tempos de compilação mais longos, erros de compilação pouco claros, inchaço do código e problemas de portabilidade entre os compiladores
  • escrever código com exceção de segurança é difícil, o que facilita a introdução de erros sutis durante a conversão
  • é mais complicado usar o código C ++ de uma biblioteca compartilhada do que o C simples
  • C ++ não é tão amplamente suportado como, por exemplo, C89

Para concluir, a conversão de códigos C e C ++ mistos para C ++ idiomático deve ser vista como uma troca que tem muito mais do que apenas tempo de implementação e ampliando sua caixa de ferramentas com recursos aparentemente convenientes. Portanto, é muito difícil dar uma resposta para o caso geral que não seja "depende".

crafn
fonte
"fluxos de biblioteca padrão são mais lentos que os equivalentes C": provavelmente, certamente mais difícil para a internacionalização do que o printf de qualquer maneira.
Deduplicator
1

É uma má forma misturar C e C ++. Eles são idiomas distintos e realmente devem ser tratados como tal. Escolha a que mais lhe agrada e tente escrever código idiomático nesse idioma.

Se C ++ estiver poupando muito código, fique com C ++ e reescreva as partes C. Duvido que uma diferença de desempenho seja perceptível, se é com isso que você está preocupado.

Oleksi
fonte
Então, eu tenho essa biblioteca que eu quero usar, que está escrita em C, e eu tenho essa outra biblioteca que eu quero usar, que está escrita em C ++ ... Reescrever código que funciona muito bem é apenas criar trabalho desnecessário.
gnasher729
1

Eu ando de um lado para o outro entre C e C ++ o tempo todo e sou o tipo ímpar que prefere trabalhar dessa maneira. Prefiro C ++ para coisas de nível superior, enquanto uso o C como um instrumento contundente que me permite radiografar tipos de dados e tratá-los como bits e bytes com, digamos, memcpyque acho úteis para implementar estruturas de dados de baixo nível e alocadores de memória . Se você estiver trabalhando no nível de bits e bytes brutos, o sistema de tipos realmente rico do C ++ não ajuda em nada, e geralmente acho mais fácil escrever esse código em C, onde posso assumir com segurança que posso trate qualquer tipo de dados C como apenas bits e bytes.

A principal coisa a ter em mente é onde essas duas línguas não se combinam bem.

1. A função AC nunca deve chamar uma função C ++ que possa throw. Dado quantos lugares seu tipo diário de código C ++ pode implicitamente executar em uma exceção, isso geralmente significa que suas funções C não devem chamar as funções C ++ em geral. Caso contrário, seu código C não poderá liberar os recursos que ele aloca durante o desenrolamento da pilha, pois não há uma maneira prática de capturar a exceção C ++. Se você acaba exigindo que seu código C chame uma função C ++ como retorno de chamada, por exemplo, o lado C ++ deve garantir que qualquer exceção encontrada seja capturada antes de retornar ao código C.

2. Se você escrever um código C que trata os tipos de dados apenas como bits e bytes brutos, fazendo um bulldozing no sistema de tipos (esse é realmente o meu principal motivo para usar C em primeiro lugar), nunca desejará usar esse código nos dados do C ++ tipos que podem ter construtores e destruidores de cópia, ponteiros virtuais e coisas desse tipo que precisam ser respeitadas. Portanto, geralmente deve ser o código C usando o código C desse tipo, não o código C ++ usando o código C desse tipo.

Se você deseja que seu código C ++ use esse código C, normalmente você deseja usá-lo como o detalhe de implementação de uma classe que garante que os dados que ele está armazenando na estrutura de dados C genérica são um tipo de dados que é fácil de construir. e destruir. Felizmente, existem características de tipo como essas que você pode usar para verificar isso em uma declaração estática, certificando-se de que os tipos que você está armazenando na estrutura de dados C genérica tenham destruidores e construtores triviais e permanecerão assim.

Devo procurar reescrever tudo em C ++ (com isso, quero dizer implementar mais objetos e métodos C ++, onde talvez eu tenha codificado algo em um estilo C que possa ser condensado usando técnicas mais recentes de C ++) ou remover o uso de objetos de streamer de string e trazer tudo "de volta" ao código C?

Na minha opinião, você não precisa se preocupar se seguir as regras acima e garantir que seu código seja bem testado nos testes que você escreveu. A parte que pode valer a pena melhorar é o código que você escreveu em C ++ até agora, mas isso não significa que você precise portar o código estritamente baseado em C para C ++.

Com o código que você escreveu em C ++ e compila como código em C ++, é preciso ter cuidado. Usar codificação do tipo C em C ++ está causando problemas. Por exemplo, se você alocar e liberar recursos manualmente, é provável que seu código não seja seguro para exceções, pois ele pode encontrar uma exceção no momento em que você sai implicitamente da função antes de poder utilizá free-lo. No C ++, o RAII e coisas como ponteiros inteligentes não são uma conveniência simples, pois podem aparecer se você apenas olhar para os caminhos de execução normais sem considerar caminhos excepcionais. Geralmente, são uma necessidade fundamental de escrever um código correto e seguro para exceções de maneira simples e eficaz, que não começará a vazar por todo o lugar ao encontrar uma exceção.


fonte