まず、主観的な意見を求めているように見えるかもしれませんが、それは私が望んでいることではありません。このトピックに関する根拠のある議論を聞きたいです。
最新のストリーム/シリアル化フレームワークがどのように設計されるべきかについての洞察を得るために、私は最近自分自身に本のコピーを手に入れました 標準C++ IOStreamsおよびロケールAngelika LangerおよびKlaus Kreftによる 。 IOStreamsが適切に設計されていなければ、そもそもC++標準ライブラリに入れられなかっただろうと思いました。
この本のさまざまな部分を読んだ後、IOStreamsを例と比較できるかどうか疑問に思っています。全体的なアーキテクチャの観点からのSTL。読んでください アレクサンダーステパノフ(STLの「発明者」)とのこのインタビュー は、STLに入った設計上の決定について学びます。
特に驚いたのは:
IOStreamsの全体的な設計を担当したのは誰であるかは不明のようです(これに関するいくつかの背景情報を読みたいです-誰でも良いリソースを知っていますか?)。
IOStreamsの直接の表面を掘り下げると、たとえば独自のクラスでIOStreamsを拡張する場合は、かなり暗号化された混乱したメンバー関数名を持つインターフェイスに到達します。 getloc
/imbue
、uflow
/underflow
、snextc
/sbumpc
/sgetc
/sgetn
、pbase
/pptr
/epptr
(さらに悪い例もあります)。これにより、全体的な設計と単一部品の連携方法を理解することが非常に難しくなります。上記の本でさえthatあまり役に立たない(IMHO)。
従って私の質問:
今日のソフトウェアエンジニアリング標準(実際にがこれらの一般的な合意がある場合)で判断しなければならなかった場合、C++のIOStreamsはまだ適切に設計されていると考えられますか? (ソフトウェア設計スキルを、一般に時代遅れと考えられているものから向上させたくありません。)
いくつかの不適切なアイデアが標準に組み込まれました:auto_ptr
、vector<bool>
、valarray
およびexport
、いくつか例を挙げます。したがって、IOStreamsの存在を必ずしも高品質のデザインの兆候とは見なしません。
IOStreamsにはチェッカー履歴があります。これらは実際には以前のストリームライブラリを作り直したものですが、今日のC++イディオムの多くが存在しなかったときに作成されたため、デザイナーは後知恵の恩恵を受けていませんでした。時間が経つにつれて明らかになった問題の1つは、仮想関数を大量に使用し、さらに細かい粒度で内部バッファーオブジェクトに転送するため、Cのstdioと同じくらい効率的にIOStreamsを実装することはほとんど不可能であり、また不可解な奇妙さのおかげでもあることですロケールが定義および実装される方法。これについての私の記憶はかなり曖昧です、私は認めます。 comp.lang.c ++。moderatedで、数年前に激しい議論の対象になったことを覚えています。
誰がそれらを設計したかに関して、元のライブラリは(驚くことではないが)Bjarne Stroustrupによって作成され、Dave Presottoによって再実装されました。その後、Jerry SchwarzがCfront 2.0向けに、Andrew Koenigのマニピュレーターのアイデアを使用して、これを再設計および再実装しました。ライブラリの標準バージョンは、この実装に基づいています。
ソース「C++の設計と進化」、セクション8.3.1。
純粋な意見であるため、これを別の回答として投稿しています。
入出力(特に入力)を実行することは非常に難しい問題です。そのため、iostreamsライブラリには、完全な後知恵でより良くできた可能性があります。しかし、私には、どのような言語のすべてのI/Oライブラリもこのように見えます。 I/Oシステムがそのデザイナーにa敬の念を抱かせた美しさであるプログラミング言語を使用したことはありません。 iostreamsライブラリには、特にCI/Oライブラリ(拡張性、型安全性など)に比べて利点がありますが、素晴らしいOOの例としてそれを支持している人はいないと思います。または一般的なデザイン。
C++ iostreamsに関する私の意見は、特に私自身のストリームクラスを実装することで実際に拡張し始めた後、特に大幅に改善されました。 xsputn
などのように途方もなく貧弱なメンバー関数名にもかかわらず、私は拡張性と全体的なデザインに感謝し始めました。とにかく、I/OストリームはC stdio.hを大幅に改善したものだと思います。Cstdio.hは型の安全性がなく、主要なセキュリティ上の欠陥に満ちています。
IOストリームの主な問題は、2つの関連するが多少直交する概念を統合することだと思います:テキスト形式とシリアル化。一方、IOストリームはオブジェクトの人間が読める形式のテキスト表現を生成するように設計されている一方で、オブジェクトを移植可能な形式にシリアル化するために設計されています。 。 例えば:
_std::stringstream ss;
std::string output_string = "Hello world";
ss << output_string;
...
std::string input_string;
ss >> input_string;
std::cout << input_string;
_
ここで、入力として取得するのは、元々ストリームに出力したものであるnotです。これは、_<<
_演算子は文字列全体を出力するのに対して、_>>
_演算子は長さ情報が格納されていないため、空白文字が見つかるまでストリームからのみ読み取るためですストリーム。したがって、「hello world」を含む文字列オブジェクトを出力しても、「hello」を含む文字列オブジェクトのみを入力します。そのため、ストリームはフォーマット機能としての目的を果たしましたが、オブジェクトを適切にシリアル化してから非シリアル化することに失敗しました。
IOストリームはシリアル化機能として設計されていませんが、その場合、実際にはinputストリームとは何ですか?さらに、実際にはI/Oストリームは、他の標準のシリアル化機能がないため、オブジェクトのシリアル化によく使用されます_boost::date_time
_または_boost::numeric::ublas::matrix
_を検討します。ここで、_<<
_演算子を使用してマトリックスオブジェクトを出力すると、 _>>
_演算子を使用して入力すると同じ正確なマトリックスを取得しますが、これを達成するために、Boostデザイナーは列カウントと行カウント情報をテキストデータとして出力に保存する必要があり、実際の人間ここでも、テキストの書式設定機能とシリアル化の厄介な組み合わせ。
他のほとんどの言語がこれら2つの機能をどのように分離しているかに注意してください。たとえば、Javaでは、書式設定はtoString()
メソッドを介して実行され、シリアル化はSerializable
インターフェイスを介して実行されます。
私の意見では、最良の解決策は、標準のcharacterベースのストリームと一緒にbyteベースのストリームを導入することでした。これらのストリームは、人間が読める形式/表示を気にすることなく、バイナリデータで動作します。これらは、C++オブジェクトを移植可能なバイトシーケンスに変換するために、シリアル化/逆シリアル化機能としてのみ使用できます。
私は常にC++ IOStreamsが不適切に設計されていることを発見しました:それらの実装は、新しい型をストリームに適切に定義することを非常に困難にします。また、io機能とフォーマット機能を組み合わせて(マニピュレータについて考えてください)。
個人的に、私が見つけた最高のストリームの設計と実装は、Adaプログラミング言語にあります。これはデカップリングのモデルであり、新しいタイプのストリームを作成する喜びであり、出力関数は使用されるストリームに関係なく常に機能します。これは、最も一般的な分母に感謝します。ストリームにバイトを出力するだけです。ストリーム関数は、バイトをストリームに入れる処理を行います。整数を16進数にフォーマットします(もちろん、フォーマット処理用に定義されたクラスメンバに相当するタイプ属性のセットがあります)
私はC++がストリームに関して同じくらい簡単だったことを望みます...
IOStreamsの設計は、拡張性と有用性の点で素晴らしいと思います。
ローカリゼーションの統合とフォーマットの統合。何ができるかを見てください:
std::cout << as::spellout << 100 << std::endl;
印刷可能:「百」または:
std::cout << translate("Good morning") << std::endl;
std::cout
に埋め込まれたロケールに応じて、「Bonjour」または「בוקרטוב」を印刷できます。
そのようなことは、iostreamが非常に柔軟であるという理由だけで実行できます。
もっとうまくやれるか?
もちろんできます!実際には改善できるものがたくさんあります...
今日、stream_buffer
から正しく導出することは非常に苦痛です。ストリームに追加のフォーマット情報を追加することは非常に簡単ですが、可能です。
しかし、何年も前に振り返ってみると、私はまだライブラリの設計が十分であり、多くの利点をもたらすことができました。
常に全体像を見ることができるわけではありませんが、拡張機能にポイントを残すと、考えていなかったポイントでもはるかに優れた能力が得られます。
(この答えは私の意見に基づいています)
IOStreamsは、同等の機能よりもはるかに複雑だと思います。 C++で書くとき、「古いスタイル」のI/Oにはcstdioヘッダーを使用しますが、これははるかに予測可能です。ちなみに、(実際には重要ではありませんが、絶対的な時間差はごくわずかです)IOStreamsはC I/Oよりも遅いことが多くの場合実証されています。
IOStreamを使用するとき、私はいつも驚きに会います。
ライブラリはバイナリ指向ではなくテキスト指向のようです。それは最初の驚きかもしれません:ファイルストリームでバイナリフラグを使用するだけでは、バイナリの動作を得るには不十分です。上記のユーザーチャールズサルビアは、それを正しく観察しました:IOStreamsは、フォーマットの側面(フロートの限られた数字など)をシリアル化の側面(情報の損失が望ましくない場合)と混合します。おそらく、これらの側面を分離するのが良いでしょう。 Boost.Serializationはこの半分を行います。必要に応じて、インサーターとエクストラクターにルーティングするシリアル化機能があります。すでに両方の側面の間に緊張があります。
多くの関数には、混乱するセマンティクスもあります(get、getline、ignore、readなど。区切り文字を抽出するもの、抽出しないもの、eofを設定するものなど)。さらにいくつかの言及では、ストリームを実装するときの奇妙な関数名(例えば、xsputn、uflow、underflow)。 wchar_tバリアントを使用すると、事態はさらに悪化します。 wifstreamはマルチバイトへの変換を行いますが、wstringstreamは行いません。バイナリI/Oはwchar_tですぐに動作しません。codecvtを上書きします。
CバッファーI/O(つまり、FILE)は、C++の対応するものほど強力ではありませんが、より透過的で、直感に反する動作がはるかに少ないです。
それでも、IOStreamにつまずくたびに、私はIOに火を放つように惹かれます。本当に賢い人が全体的なアーキテクチャをよく見ることができれば、おそらく良いことでしょう。
C++のiostreamには、他の応答で指摘されているように、多くの欠陥がありますが、その防御に何か注意があります。
C++は、真剣に使用されている言語の中で事実上ユニークであり、初心者にとって変数の入力と出力を簡単にします。他の言語では、ユーザー入力は型強制または文字列フォーマッタを伴う傾向がありますが、C++はコンパイラにすべての作業を行わせます。出力に関しても同じことが言えますが、C++はこの点でそれほどユニークではありません。それでも、教育的に有用なクラスやオブジェクト指向の概念を理解しなくても、フォーマット構文を理解しなくても、C++でフォーマットされたI/Oをかなりうまく行うことができます。繰り返しますが、初心者に教える場合、それは大きなプラスです。
この初心者向けのシンプルさには代償が伴いますが、より複雑な状況でI/Oを処理するための頭痛の種になる可能性がありますが、それまでにプログラマーがそれらに対処するのに十分なことを学んでいるか、少なくとも十分に古くなっていることを願っています飲む。
質問の最初の部分に答えるしかありません(誰がやったのですか?)。しかし、それは他の投稿で回答されました。
質問の2番目の部分(よく設計されていますか?)については、私の答えは圧倒的な「いいえ!」です。ここに、何年も前から信じられない思いで頭を振る小さな例を示します。
#include <stdint.h>
#include <iostream>
#include <vector>
// A small attempt in generic programming ;)
template <class _T>
void ShowVector( const char *title, const std::vector<_T> &v)
{
std::vector<_T>::const_iterator iter;
std::cout << title << " (" << v.size() << " elements): ";
for( iter = v.begin(); iter != v.end(); ++iter )
{
std::cout << (*iter) << " ";
}
std::cout << std::endl;
}
int main( int argc, const char * argv[] )
{
std::vector<uint8_t> byteVector;
std::vector<uint16_t> wordVector;
byteVector.Push_back( 42 );
wordVector.Push_back( 42 );
ShowVector( "Garbled bytes as characters output o.O", byteVector );
ShowVector( "With words, the numbers show as numbers.", wordVector );
return 0;
}
上記のコードは、iostreamの設計によりナンセンスを生成します。私の理解を超えたいくつかの理由で、uint8_tバイトを文字として扱いますが、より大きな整数型は数字のように扱われます。 Q.e.d.悪いデザイン。
また、これを修正する方法はありません。タイプは、代わりにフロートまたはダブルにすることもできます...そのため、「int」へのキャストは、愚かなiostreamに、文字ではなく数字がトピックであることを理解させるのに役立ちません。
私の返事に対する反対票を受け取った後、おそらくもう少し説明をしましょう... IOStreamの設計は、プログラマーにアイテムの処理方法を述べる手段を与えないため、欠陥があります。 IOStream実装は、任意の決定を行います(uint8_tをバイト数ではなくcharとして扱うなど)。これはIS IOStreamデザインの欠陥です。彼らは達成不可能なものを達成しようとしています。
C++では、型を分類することはできません-言語には機能がありません。 is_number_type()やis_character_type()など、IOStreamが適切な自動選択を行うために使用できるものはありません。それを無視して、推測ISライブラリの設計上の欠陥。
認められているように、printf()は一般的な「ShowVector()」実装でも同様に機能しません。しかし、それはiostream動作の言い訳にはなりません。しかし、printf()の場合、ShowVector()は次のように定義される可能性が非常に高くなります。
template <class _T>
void ShowVector( const char *formatString, const char *title, const std::vector<_T> &v );