Críticas ao C ++ - Criticism of C++

C ++ é uma linguagem de programação de propósito geral com imperativo , orientado a objetos , e genéricas recursos de programação. Muitas críticas foram feitas ao design do C ++ por desenvolvedores de software conhecidos, incluindo Linus Torvalds , Richard Stallman , Joshua Bloch , Rob Pike , Ken Thompson e Donald Knuth .

C ++ é uma linguagem multi-paradigma de programação com extensa, mas não completa, a compatibilidade com C . Este artigo não se concentra nos recursos do C, como aritmética de ponteiros , precedência do operador ou macros do pré - processador , mas nos recursos do C ++ puro que são frequentemente criticados.

Tempos de compilação lentos

A interface natural entre os arquivos de origem em C / C ++ são os arquivos de cabeçalho . Cada vez que um arquivo de cabeçalho é modificado, todos os arquivos de origem que incluem o arquivo de cabeçalho devem recompilar seu código. Os arquivos de cabeçalho são lentos porque são textuais e dependentes do contexto como consequência do pré-processador. C possui apenas quantidades limitadas de informações em arquivos de cabeçalho, sendo as mais importantes declarações de estruturas e protótipos de funções. C ++ armazena suas classes em arquivos de cabeçalho e eles não apenas expõem suas variáveis ​​públicas e funções públicas (como C com suas estruturas e protótipos de função), mas também suas funções privadas. Isso força recompilações desnecessárias de todos os arquivos de origem que incluem o arquivo de cabeçalho, sempre que alterar essas funções privadas. Esse problema é ampliado quando as classes são escritas como modelos , forçando todo o seu código para os arquivos de cabeçalho lentos, o que é o caso de toda a biblioteca padrão C ++ . Grandes projetos C ++ podem, portanto, ser relativamente lentos para compilar. O problema é amplamente resolvido por cabeçalhos pré-compilados em compiladores modernos ou usando o sistema de módulo que foi adicionado em C ++ 20 ; futuros padrões C ++ estão planejando expor a funcionalidade da biblioteca padrão usando módulos.

Estado de formato global de <iostream>

C ++ <iostream>, ao contrário de C <stdio.h>, depende de um estado de formato global. Isso se encaixa muito mal com exceções , quando uma função deve interromper o fluxo de controle, após um erro, mas antes de redefinir o estado de formato global. Uma correção para isso é usar Resource Acquisition Is Initialization (RAII), que é implementado nas bibliotecas Boost e parte da C ++ Standard Library .

<iostream>usa construtores estáticos que causam sobrecarga inútil se incluídos, mesmo se a biblioteca não for usada. Outra fonte de desempenho ruim é o uso indevido de em std::endlvez de \nao fazer a saída, como também chama .flush(). C ++ <iostream>é por padrão sincronizado com o <stdio.h>que pode causar problemas de desempenho em aplicativos intensivos de io de linha de comando. Desligá-lo pode melhorar o desempenho, mas força a renúncia de algumas garantias de pedido.

Aqui segue um exemplo onde uma exceção interrompe a função antes de std::coutpoder ser restaurada de hexadecimal para decimal. O número do erro na instrução catch será escrito em hexadecimal, o que provavelmente não é o que se deseja:

#include <iostream>
#include <vector>

int main() {
  try {
    std::cout << std::hex
              << 0xFFFFFFFF << '\n';
    // std::bad_alloc will be thrown here:
    std::vector<int> vector(0xFFFFFFFFFFFFFFFFull);
    std::cout << std::dec; // Never reached
                           // (using scopes guards would have fixed that issue 
                           //  and made the code more expressive)
  } 
  catch (const std::exception& e) {
    std::cout << "Error number: " << 10 << '\n';  // Not in decimal
  }
}

É até reconhecido por alguns membros do corpo de padrões C ++ que <iostream>é uma interface envelhecida que eventualmente precisa ser substituída. Esse design força os implementadores da biblioteca a adotar soluções que afetam bastante o desempenho.

O C ++ 20 adicionou std::formatque eliminou o estado de formatação global e tratou de outros problemas no iostreams. Por exemplo, a cláusula catch agora pode ser escrita como

std::cout << std::format("Error number: {}\n", 10);

que não é afetado pelo estado do fluxo. Embora possa introduzir sobrecarga devido à formatação real sendo feita no tempo de execução.

Iteradores

A filosofia da Standard Template Library (STL) embutida na C ++ Standard Library é usar algoritmos genéricos na forma de modelos usando iteradores . Os primeiros compiladores otimizaram mal pequenos objetos, como iteradores, o que Alexander Stepanov caracterizou como a "penalidade de abstração", embora os compiladores modernos otimizem bem essas pequenas abstrações. A interface que usa pares de iteradores para denotar intervalos de elementos também foi criticada. A introdução de intervalos da biblioteca padrão C ++ 20 deve resolver este problema.

Um grande problema é que os iteradores geralmente lidam com dados alocados por heap nos contêineres C ++ e se tornam inválidos se os dados forem movidos independentemente pelos contêineres. As funções que alteram o tamanho do contêiner geralmente invalidam todos os iteradores que apontam para ele, criando casos perigosos de comportamento indefinido . Aqui está um exemplo em que os iteradores no loop for são invalidados devido à std::stringalteração do tamanho do contêiner no heap :

#include <iostream>
#include <string>

int main() {
  std::string text = "One\nTwo\nThree\nFour\n";
  // Let's add an '!' where we find newlines
  for (auto it = text.begin(); it != text.end(); ++it) {
    if (*it == '\n') {
      // it =
      text.insert(it, '!') + 1;
      // Without updating the iterator this program has
      // undefined behavior and will likely crash
    }
  }
  std::cout << text;
}

Sintaxe de inicialização uniforme

A sintaxe de inicialização uniforme do C ++ 11 e std :: initializer_list compartilham a mesma sintaxe, que é acionada de forma diferente dependendo do funcionamento interno das classes. Se houver um construtor std :: initializer_list, ele é chamado. Caso contrário, os construtores normais são chamados com a sintaxe de inicialização uniforme. Isso pode ser confuso para iniciantes e especialistas

#include <iostream>
#include <vector>

int main() {
  int integer1{10};                 // int
  int integer2(10);                 // int
  std::vector<int> vector1{10, 0};  // std::initializer_list
  std::vector<int> vector2(10, 0);  // std::size_t, int

  std::cout << "Will print 10\n" << integer1 << '\n';
  std::cout << "Will print 10\n" << integer2 << '\n';

  std::cout << "Will print 10,0,\n";

  for (const auto& item : vector1) {
    std::cout << item << ',';
  }

  std::cout << "\nWill print 0,0,0,0,0,0,0,0,0,0,\n";

  for (const auto& item : vector2) {
    std::cout << item << ',';
  }
}

Exceções

Tem havido preocupação de que o princípio da sobrecarga zero não seja compatível com exceções. A maioria das implementações modernas tem uma sobrecarga de desempenho zero quando as exceções são habilitadas, mas não são usadas, mas têm uma sobrecarga durante o tratamento de exceções e no tamanho binário devido à necessidade de desfazer tabelas. Muitos compiladores oferecem suporte à desativação de exceções da linguagem para salvar a sobrecarga binária. As exceções também foram criticadas por serem inseguras para o controle do estado. Esse problema de segurança levou à invenção do idioma RAII, que se mostrou útil além de tornar seguras as exceções do C ++.

Codificação de literais de string no código-fonte

Literais de string C ++, como aqueles de C, não consideram a codificação de caracteres do texto dentro deles: eles são meramente uma sequência de bytes, e a stringclasse C ++ segue o mesmo princípio. Embora o código-fonte possa (desde C ++ 11) solicitar uma codificação para um literal, o compilador não tenta validar se a codificação escolhida do literal de origem está "correta" para os bytes colocados nele, e o tempo de execução não impor a codificação de caracteres. Os programadores que estão acostumados com outras linguagens como Java, Python ou C #, que tentam forçar a codificação de caracteres, muitas vezes consideram isso um defeito da linguagem.

O programa de exemplo abaixo ilustra o fenômeno.

#include <iostream>
#include <string>
// note that this code is no longer valid in C++20
int main() {
  // all strings are declared with the UTF-8 prefix

  // file encoding determines the encoding of å and Ö
  std::string auto_enc = u8"Vår gård på Öland!";
  // this text is well-formed in both ISO-8859-1 and UTF-8
  std::string ascii = u8"Var gard pa Oland!";
  // explicitly use the ISO-8859-1 byte-values for å and Ö
  // this is invalid UTF-8
  std::string iso8859_1 = u8"V\xE5r g\xE5rd p\xE5 \xD6land!";
  // explicitly use the UTF-8 byte sequences for å and Ö
  // this will display incorrectly in ISO-8859-1
  std::string utf8 = u8"V\xC3\xA5r g\xC3\xA5rd p\xC3\xA5 \xC3\x96land!";

  std::cout << "byte-count of automatically-chosen, [" << auto_enc
            << "] = " << auto_enc.length() << '\n';
  std::cout << "byte-count of ASCII-only [" << ascii << "] = " << ascii.length()
            << '\n';
  std::cout << "byte-count of explicit ISO-8859-1 bytes [" << iso8859_1
            << "] = " << iso8859_1.length() << '\n';
  std::cout << "byte-count of explicit UTF-8 bytes [" << utf8
            << "] = " << utf8.length() << '\n';
}

Apesar da presença do prefixo C ++ 11 'u8', que significa "string literal Unicode UTF-8", a saída deste programa realmente depende da codificação de texto do arquivo de origem (ou das configurações do compilador - a maioria dos compiladores pode ser instruída a converter arquivos de origem para uma codificação específica antes de compilá-los). Quando o arquivo de origem é codificado usando UTF-8 e a saída é executada em um terminal configurado para tratar sua entrada como UTF-8, a seguinte saída é obtida:

byte-count of automatically-chosen, [Vår gård på Öland!] = 22
byte-count of ASCII-only [Var gard pa Oland!] = 18
byte-count of explicit ISO-8859-1 bytes [Vr grd p land!] = 18
byte-count of explicit UTF-8 bytes [Vår gård på Öland!] = 22

O terminal de saída removeu os bytes UTF-8 inválidos da exibição na string de exemplo ISO-8859. Passar a saída do programa por um utilitário Hex dump revelará que eles ainda estão presentes na saída do programa e foi o aplicativo de terminal que os removeu.

No entanto, quando o mesmo arquivo de origem é salvo em ISO-8859-1 e recompilado, a saída do programa no mesmo terminal torna-se:

byte-count of automatically-chosen, [Vr grd p land!] = 18
byte-count of ASCII-only [Var gard pa Oland!] = 18
byte-count of explicit ISO-8859-1 bytes [Vr grd p land!] = 18
byte-count of explicit UTF-8 bytes [Vår gård på Öland!] = 22

Uma solução proposta é tornar a codificação de origem confiável em todos os compiladores.

Inchaço de código

Algumas implementações mais antigas de C ++ foram acusadas de gerar inchaço de código .

Veja também

Referências

Trabalhos citados

Leitura adicional

  • Ian Joyner (1999). Objetos não encapsulados: Java, Eiffel e C ++ ?? (Tecnologia de objeto e componente) . Prentice Hall PTR; 1ª edição. ISBN 978-0130142696.
  • Peter Seibel (2009). Coders at Work: Reflections on the Craft of Programming . Apress. ISBN 978-1430219484.

links externos