Primeiro, pode parecer que estou pedindo opiniões subjetivas, mas não é isso que estou procurando. Eu adoraria ouvir alguns argumentos bem fundamentados sobre esse tópico.
Na esperança de ter uma ideia de como deve ser projetada uma estrutura moderna de fluxos / serialização, recentemente consegui uma cópia do livro Standard C ++ IOStreams and Locales de Angelika Langer e Klaus Kreft . Imaginei que, se o IOStreams não fosse bem projetado, ele não teria entrado na biblioteca padrão C ++ em primeiro lugar.
Depois de ler várias partes deste livro, estou começando a ter dúvidas se o IOStreams pode ser comparado com, por exemplo, o STL de um ponto de vista geral da arquitetura. Leia, por exemplo, esta entrevista com Alexander Stepanov (o "inventor" do STL) para aprender sobre algumas decisões de design que entraram no STL.
O que me surpreende em particular :
Parece ser desconhecido quem foi responsável pelo design geral do IOStreams (eu adoraria ler algumas informações básicas sobre isso - alguém conhece bons recursos?);
Uma vez que você mergulhar abaixo da superfície imediata de iostreams, por exemplo, se você deseja estender iostreams com suas próprias classes, você começa a uma interface com nomes de função de membro bastante enigmáticos e confuso, por exemplo,
getloc
/imbue
,uflow
/underflow
,snextc
/sbumpc
/sgetc
/sgetn
,pbase
/pptr
/epptr
(e não há exemplos provavelmente ainda piores). Isso torna muito mais difícil entender o design geral e como as peças únicas cooperam. Mesmo o livro que eu mencionei acima não ajuda que muito (IMHO).
Assim, minha pergunta:
Se você tivesse que julgar pelos padrões de engenharia de software de hoje (se há realmente é qualquer acordo geral sobre estes), seria 's C ++ iostreams ainda ser considerada bem concebido? (Eu não gostaria de melhorar minhas habilidades de design de software de algo que geralmente é considerado desatualizado.)
std::streambuf
é a classe base para leitura e gravação de bytes eistream
/ostream
é para entrada e saída formatada, tendo um ponteirostd::streambuf
como destino / fonte.ostream foo(&somebuffer); foo << "huh"; foo.rdbuf(cout.rdbuf()); foo << "see me!";
Respostas:
Várias idéias mal concebidas encontraram seu caminho para o padrão:
auto_ptr
,vector<bool>
,valarray
eexport
, só para citar alguns. Portanto, eu não consideraria a presença do IOStreams necessariamente como um sinal de design de qualidade.Os IOStreams têm um histórico de xadrez. Eles são, na verdade, uma reformulação de uma biblioteca de fluxos anterior, mas foram criados em um período em que muitos dos idiomas C ++ atuais não existiam, portanto os designers não tiveram o benefício da retrospectiva. Um problema que só se tornou aparente com o tempo foi que é quase impossível implementar IOStreams tão eficientemente quanto o stdio de C, devido ao uso abundante de funções virtuais e encaminhamento para objetos de buffer interno, mesmo com a granularidade mais fina, e também graças a alguma estranheza inescrutável na maneira como as localidades são definidas e implementadas. Minha lembrança disso é bastante confusa, admito; Lembro-me de ter sido objeto de intenso debate há alguns anos, sobre o comp.lang.c ++. Moderado.
fonte
comp.lang.c++.moderated
arquivo e postar links na parte inferior da minha pergunta se encontrar algo valioso. - Além disso, ouso discordar de vocêauto_ptr
: Após ler o C ++ excepcional de Herb Sutter , parece uma classe muito útil ao implementar o padrão RAII.unique_ptr
uma semântica mais clara e mais poderosa.unique_ptr
requer referência rvalue. Então, neste ponto,auto_ptr
é um ponteiro muito poderoso.auto_ptr
tem asneira semântica copy / atribuição que o tornam um nicho para dereferencing erros ...Com relação a quem os projetou, a biblioteca original foi (sem surpresa) criada por Bjarne Stroustrup e depois reimplementada por Dave Presotto. Isso foi redesenhado e reimplementado novamente por Jerry Schwarz para o Cfront 2.0, usando a idéia de manipuladores de Andrew Koenig. A versão padrão da biblioteca é baseada nesta implementação.
Fonte "O Design e Evolução do C ++", seção 8.3.1.
fonte
Eu diria NÃO , por várias razões:
Má manipulação de erros
As condições de erro devem ser relatadas com exceções, não com
operator void*
.O antipadrão "objeto zumbi" é o que causa erros como esses .
Má separação entre formatação e E / S
Isso torna os objetos de fluxo desnecessariamente complexos, pois eles precisam conter informações adicionais de estado para formatação, independentemente de você precisar ou não.
Também aumenta as chances de escrever bugs como:
Se, em vez disso, você escreveu algo como:
Não haveria bits de estado relacionados à formatação e nenhum problema.
Note-se que em linguagens "modernas" como Java, C # e Python, todos os objetos têm um
toString
/ToString
/__str__
função que é chamada pelas rotinas de I / O. AFAIK, apenas o C ++ faz o contrário, usandostringstream
como a maneira padrão de converter em uma string.Suporte deficiente para i18n
A saída baseada em Iostream divide literais de string em pedaços.
As strings de formato colocam frases inteiras em literais de strings.
A última abordagem é mais fácil de se adaptar a bibliotecas de internacionalização como o GNU gettext, porque o uso de frases inteiras fornece mais contexto para os tradutores. Se a sua rotina de formatação de string suportar reordenar (como os
$
parâmetros POSIX printf), também será melhor lidar com as diferenças na ordem das palavras entre os idiomas.fonte
$
especificadores POSIXprintf
.Estou postando isso como uma resposta separada, porque é pura opinião.
Executar entrada e saída (particularmente entrada) é um problema muito, muito difícil; portanto, não é de surpreender que a biblioteca iostreams esteja cheia de itens e coisas que, com uma retrospectiva perfeita, poderiam ter sido melhores. Mas parece-me que todas as bibliotecas de E / S, em qualquer idioma, são assim. Eu nunca usei uma linguagem de programação em que o sistema de E / S fosse uma coisa de beleza que me fez admirar seu designer. A biblioteca iostreams tem vantagens, principalmente sobre a biblioteca de CI / O (extensibilidade, segurança de tipo etc.), mas não acho que alguém a esteja segurando como um exemplo de excelente OO ou design genérico.
fonte
Minha opinião sobre o C ++ iostreams melhorou substancialmente com o tempo, principalmente depois que comecei a estendê-los implementando minhas próprias classes de fluxo. Comecei a apreciar a extensibilidade e o design geral, apesar dos nomes ridiculamente pobres de funções de membro, como o que
xsputn
seja. Independentemente disso, acho que os fluxos de E / S são uma grande melhoria em relação ao C stdio.h, que não possui segurança de tipo e está repleto de grandes falhas de segurança.Penso que o principal problema dos fluxos de IO é que eles conflitam dois conceitos relacionados, mas de certa forma ortogonais: formatação textual e serialização. Por um lado, os fluxos de E / S são projetados para produzir uma representação textual formatada, legível por humanos, de um objeto e, por outro lado, para serializar um objeto em um formato portátil. Às vezes, esses dois objetivos são o mesmo, mas outras vezes isso resulta em incongruências seriamente irritantes. Por exemplo:
Aqui, o que obtemos como entrada não é o que originalmente produzimos para o fluxo. Isso ocorre porque o
<<
operador gera a string inteira, enquanto o>>
operador só lê o fluxo até encontrar um caractere de espaço em branco, pois não há informações de tamanho armazenadas no fluxo. Portanto, mesmo que produzimos um objeto de string contendo "hello world", apenas inseriremos um objeto de string contendo "hello". Portanto, embora o fluxo tenha servido a seu objetivo como um recurso de formatação, ele não conseguiu serializar corretamente e depois desserializar o objeto.Você pode dizer que os fluxos de E / S não foram projetados para serem recursos de serialização, mas se for esse o caso, para que servem realmente os fluxos de entrada ? Além disso, na prática, os fluxos de E / S costumam ser usados para serializar objetos, porque não existem outros recursos de serialização padrão. Considere
boost::date_time
ouboost::numeric::ublas::matrix
, onde, se você produzir um objeto de matriz com o<<
operador, obterá a mesma matriz exata ao inseri-lo usando o>>
operador. Mas, para conseguir isso, os designers do Boost tiveram que armazenar informações de contagem de colunas e de linhas como dados textuais na saída, o que compromete a exibição legível por humanos. Novamente, uma combinação estranha de recursos de formatação de texto e serialização.Observe como a maioria dos outros idiomas separa esses dois recursos. Em Java, por exemplo, a formatação é realizada através do
toString()
método, enquanto a serialização é realizada através daSerializable
interface.Na minha opinião, a melhor solução seria a introdução de fluxos baseados em bytes , juntamente com os fluxos padrão baseados em caracteres . Esses fluxos operariam com dados binários, sem preocupação com a formatação / exibição legível por humanos. Eles poderiam ser usados apenas como recursos de serialização / desserialização, para converter objetos C ++ em sequências de bytes portáteis.
fonte
std::char_traits
que não pode ser portably especializado em tirar umaunsigned char
. No entanto, existem soluções alternativas, então acho que a extensibilidade vem ao resgate mais uma vez. Mas acho que o fato de que os fluxos baseados em bytes não são padrão é uma fraqueza da biblioteca.std::streambuf
. Então, basicamente, a única coisa que você está estendendo é astd::basic_ios
classe. Portanto, há uma linha na qual "estender" passa para o território "completamente reimplementando" e a criação de um fluxo binário a partir das instalações de fluxo de E / S C ++ parece se aproximar desse ponto.Eu sempre achei C ++ IOStreams mal projetados: sua implementação dificulta a definição adequada de um novo tipo de fluxo. eles também misturam recursos io e recursos de formatação (pense em manipuladores).
pessoalmente, o melhor design e implementação de stream que eu já encontrei está na linguagem de programação Ada. é um modelo de dissociação, uma alegria em criar novos tipos de fluxos, e as funções de saída sempre funcionam independentemente do fluxo usado. isso é graças a um denominador menos comum: você gera bytes para um fluxo e é isso. As funções de fluxo cuidam de colocar os bytes no fluxo, não é tarefa deles, por exemplo, formatar um número inteiro em hexadecimal (é claro, existe um conjunto de atributos de tipo, equivalente a um membro da classe, definido para lidar com a formatação)
Eu gostaria que o C ++ fosse tão simples quanto aos fluxos ...
fonte
Eu acho que o design do IOStreams é brilhante em termos de extensibilidade e utilidade.
Integração de localização e integração de formatação. Veja o que pode ser feito:
Pode imprimir: "cem" ou até:
Pode imprimir "Bonjour" ou "בוקר טוב" de acordo com a localidade imbuída para
std::cout
!Essas coisas podem ser feitas apenas porque os iostreams são muito flexíveis.
Poderia ser feito melhor?
Claro que poderia! De fato, existem muitas coisas que poderiam ser melhoradas ...
Hoje, é bastante doloroso derivar corretamente
stream_buffer
, não é trivial adicionar informações de formatação adicionais ao fluxo, mas é possível.Mas, olhando para trás há muitos anos, eu ainda o design da biblioteca era bom o suficiente para trazer muitos presentes.
Como você nem sempre pode ver o quadro geral, mas se você deixar pontos para extensões, isso lhe dará habilidades muito melhores, mesmo em pontos em que você não pensou.
fonte
print (spellout(100));
eprint (translate("Good morning"));
Isso parece uma boa idéia, pois isso desacopla a formatação e o i18n da E / S.french_output << translate("Good morning")
:;english_output << translate("Good morning")
daria a você: "Bonjour Bom dia"out << format("text {1}") % value
e pode ser traduzido para"{1} translated"
. Então funciona bem;-)
.(Esta resposta é baseada apenas na minha opinião)
Eu acho que os IOStreams são muito mais complexos do que suas funções equivalentes. Quando escrevo em C ++, ainda uso os cabeçalhos cstdio para E / S "antiga", que acho muito mais previsível. Em uma nota lateral, (embora isso não seja realmente importante; a diferença de tempo absoluta é desprezível), os IOStreams provaram em várias ocasiões ser mais lento que o CI / O.
fonte
sstringstream
. Eu acho que a velocidade importa, embora seja secundária.Sempre encontro surpresas ao usar o IOStream.
A biblioteca parece orientada a texto e não binária. Essa pode ser a primeira surpresa: o uso do sinalizador binário em fluxos de arquivos não é suficiente para obter um comportamento binário. O usuário Charles Salvia acima observou isso corretamente: o IOStreams combina aspectos de formatação (onde você deseja uma saída bonita, por exemplo, dígitos limitados para carros alegóricos) com aspectos de serialização (onde você não deseja perda de informações). Provavelmente seria bom separar esses aspectos. Boost.Serialization faz essa metade. Você tem uma função de serialização que direciona para os insersores e extratores, se desejar. Já existe a tensão entre os dois aspectos.
Muitas funções também possuem semânticas confusas (por exemplo, get, getline, ignorar e ler. Algumas extraem o delimitador, outras não; outras também definem um eof). Além disso, alguns mencionam os nomes de funções estranhas ao implementar um fluxo (por exemplo, xsputn, uflow, underflow). As coisas ficam ainda piores quando se usa as variantes wchar_t. O wifstream faz uma tradução para multibyte enquanto o wstringstream não. A E / S binária não funciona imediatamente com wchar_t: você sobrescreve o codecvt.
A E / S com buffer c (ou seja, FILE) não é tão poderosa quanto sua contrapartida em C ++, mas é mais transparente e possui muito menos comportamento contra-intuitivo.
Ainda assim, sempre que tropeço no IOStream, sinto-me atraído por ele como uma mariposa para disparar. Provavelmente, seria bom que um cara realmente inteligente desse uma boa olhada na arquitetura geral.
fonte
Não consigo deixar de responder à primeira parte da pergunta (quem fez isso?). Mas foi respondido em outros posts.
Quanto à segunda parte da pergunta (Bem elaborada?), Minha resposta é um retumbante "Não!". Aqui está um pequeno exemplo que me faz balançar a cabeça em descrença desde anos:
O código acima produz disparates devido ao design do iostream. Por algumas razões além do meu entendimento, eles tratam os bytes uint8_t como caracteres, enquanto tipos integrais maiores são tratados como números. Qed Design ruim.
Também não há como pensar em consertar isso. O tipo também pode ser um float ou um double ... então, uma conversão para 'int' para fazer o iostream bobo entender que números e não caracteres são o tópico não ajudará.
Depois de receber um voto negativo na minha resposta, talvez mais algumas palavras de explicação ... O design do IOStream é defeituoso, pois não oferece ao programador um meio de declarar COMO um item é tratado. A implementação do IOStream toma decisões arbitrárias (como tratar uint8_t como um caractere, não como um número de bytes). Essa é uma falha do design do IOStream, pois eles tentam alcançar o inatingível.
O C ++ não permite classificar um tipo - o idioma não possui esse recurso. Não existe is_number_type () ou is_character_type () que o IOStream poderia usar para fazer uma escolha automática razoável. Ignorar isso e tentar fugir do palpite é uma falha de design de uma biblioteca.
Admitido, printf () também falharia ao trabalhar em uma implementação genérica "ShowVector ()". Mas isso não é desculpa para o comportamento do iostream. Mas é muito provável que, no caso printf (), ShowVector () seja definido assim:
fonte
uint8_t
serve o seu typedef . É realmente um char? Então não culpe o iostreams por tratá-lo como um char.num_put
faceta em vez do operador de inserção de fluxo.Os iostreams do C ++ têm muitas falhas, conforme observado nas outras respostas, mas eu gostaria de observar algo em sua defesa.
O C ++ é praticamente único entre os idiomas de uso sério, tornando a entrada e a saída variáveis fáceis para iniciantes. Em outros idiomas, a entrada do usuário tende a envolver coerção de tipo ou formatadores de string, enquanto o C ++ faz com que o compilador faça todo o trabalho. O mesmo é amplamente verdadeiro para a saída, embora o C ++ não seja tão exclusivo nesse aspecto. Ainda assim, você pode executar E / S formatada muito bem em C ++ sem precisar entender classes e conceitos orientados a objetos, o que é pedagogicamente útil e sem ter que entender a sintaxe do formato. Novamente, se você está ensinando iniciantes, é uma grande vantagem.
Essa simplicidade para iniciantes tem um preço, o que pode causar dor de cabeça ao lidar com E / S em situações mais complexas, mas, esperançosamente, nesse ponto o programador tenha aprendido o suficiente para poder lidar com eles ou, pelo menos, ter idade suficiente. para beber.
fonte