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::endl
vez de \n
ao 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::cout
poder 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::format
que 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::string
alteraçã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 string
classe 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
- Stroustrup, Bjarne (1994). O Design e a Evolução do C ++ . Addison-Wesley. ISBN 0-201-54330-3.
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
- C ++ FQA Lite por Yossi Kreinin
- C ++ O COBOL dos anos 90
- C ++ em Coders at Work Trechos do livro Coders at Work, de Peter Seibel
- DConf 2014: The Last Thing D Needs Um vídeo de uma palestra de Scott Meyers