July 24, 2007
[C++] STL を未だに賢く使えていない自分
以下のようなファイルのデータを読み込んでコンテナに格納して,それを標準出力へ出力するプログラムを書くとします.
1 2 3 4 5 6
私がすっと思いつくのが以下のコード.
#include <iostream>
#include <fstream>
#include <list>
#include <functional>
#include <memory>
typedef int Value
typedef std::list<Value> DataContainer
//! ファイルからデータを読み出す
std::auto_ptr<DataContainer> Read(const std::string& filename)
{
std::auto_ptr<DataContainer> data(new DataContainer);
std::ifstream in(filename.c_str());
for (;;)
{
int value;
in >> value;
if (!in) break;
data->push_back(value);
}
return data;
}
//! データをストリームに書き出す
struct Show : public std::binary_function<int, std::ostream*, void>
{
void operator () (int value, std::ostream* out) const
{ *out << value << "\n"; }
};
int main(int argc, char* argv[])
{
std::auto_ptr<DataContainer> data = Read(argv[1]);
std::for_each(data->begin(), data->end(), std::bind2nd(Show(), &std::cout));
return 0;
}
とはいえ,Effective STL を読んでいると,std::list のコンストラクタで直接読み込んでいたりするので,上のコードではまだ車輪の再発明をしているわけです.
それを考慮して書き直したのが下のコード.
#include <iostream>
#include <fstream>
#include <iterator>
#include <list>
#include <functional>
#include <memory>
typedef int Value
typedef std::list<Value> DataContainer
//! ファイルからデータを読み出す (STL をより賢く使ったバージョン)
std::auto_ptr<DataContainer> Read(const std::string& filename)
{
std::ifstream in(filename.c_str());
std::istream_iterator<Value> in_begin(in);
std::istream_iterator<Value> in_end;
return std::auto_ptr<DataContainer>(new DataContainer(in_begin, in_end));
}
int main(int argc, char* argv[])
{
std::auto_ptr<DataContainer> data = Read(argv[1]);
std::copy(data->begin(), data->end(), std::ostream_iterator<Value>(std::cout, "\n"));
return 0;
}
何と,Read() 関数はたった 4 行... 恐るべし STL.ちなみに実行結果はどちらのコードを用いても同じで,以下の通りの結果です.
1 2 3 4 5 6
July 17, 2007
[プログラミング] PGM ファイルの書き出し in Windows
Windows 上でプログラムを書いていて,24-bit 画像のピクセルデータを std::vector<BYTE> 上に保持し,それを PGM (P5; バイナリ形式) 形式で手っ取り早くファイルに書き出そうと思い,以下のコード片を書きました.
std::vector<BYTE> pixel_data;
...
std::ofstream out("temp.pgm");
out << "P5\n" << width << " " << height << "\n255\n"; // width と height はそれぞれ画像の幅と高さ
for (std::vector<BYTE>::iterator it = pixel_data.begin(); it != pixel_data.end(); ++it)
{
char p = *it;
out.write(&p, 1);
}
これが見事に大はまり.出力した PGM ファイルを IrfanView で見ると... 1 ピクセル横にずれるのです.原因がさっぱり分からず数時間たっぷり悩んでいました.
悩みながら,PGM (P5) の代わりに PPM (P6) で書き込むと,RGB の成分ずれが起こったので,今回の原因に気づきました.テキストモードで書き込んでいたため LF が CR+LF に変換されていたというわけです.
以下のように,バイナリモードでファイルを開いて書き込めば万事解決.
std::vector<BYTE> pixel_data;
...
std::ofstream out("temp.pgm", std::ios::binary);
out << "P5\n" << width << " " << height << "\n255\n"; // width と height はそれぞれ画像の幅と高さ
for (std::vector<BYTE>::iterator it = pixel_data.begin(); it != pixel_data.end(); ++it)
{
char p = *it;
out.write(&p, 1);
}
今まで散々 LF オンリーな環境でプログラムを書いていたので,全然気づきませんでした.あー,なんて凡ミスをしてるんだろ俺.
[C++] 宣言ならば値で受け渡す引数に不完全型を使っても良い
最近 C++ で pimpl イディオム(コンパイラファイアウォール)を使ってヘッダファイル間の依存関係を減らすことを(くだらないクラス型に対しても)積極的に実践しています.
pimpl は不完全型(incomplete type; 定義がなく宣言のみの型)へのポインタもしくはリファレンスが使えることを前提に成り立っているわけですが,私は不完全型はポインタもしくはリファレンスだけにしか適用できないと思い込んでました.
Exceptional C++ の 4 章を読んでいると,値渡し,あるいは値返しをする関数の宣言に不完全型が使われていました.
「え!? こんなことできるの!?」と思ってしまいました.
というのも,ポインタやリファレンスについては,指し示す先の具体的な情報を知らなくていいというのが直感で分かりますのでいいとして,これが値で受け渡す場合にも通用するとは思っていなかったのです.
思い込みとは恐ろしいものです.試しにコードを書いてみると...
// test.cc
class A;
A func();
% g++ -c test.cc %
通りました.宣言だけなら不完全型で大丈夫のようです.
もちろん使用するには完全型である必要がありますので,以下の 2 つのコードはコンパイルエラーです.
// test1.cc
class A;
A func(); // ただの宣言
int main()
{
A a1; // Error. 不完全型のインスタンスを定義することはできない
A a2 = func(); // Error. 不完全型の初期化はできない
return 0;
}
// test2.cc
class A;
A* func1(); // ただの宣言
A func2()
{
return *func1(); // Error. 不完全型を値返しすることはできない
}
int main()
{
return 0;
}
% g++ -c test1.cc test1.cc: In function `int main()': test1.cc:8: error: aggregate `A a1' has incomplete type and cannot be defined test1.cc:9: error: variable `A a2' has initializer but incomplete type test1.cc:9: error: invalid use of undefined type `struct A' test1.cc:2: error: forward declaration of `struct A' % g++ -c test2.cc test2.cc: In function `A func2()': test2.cc:6: error: return type `struct A' is incomplete test2.cc:7: confused by earlier errors, bailing out %
ということは,これでさらにヘッダ内の #include を減らすことができる,というわけです.
C++ は全くもって複雑で,調べれば調べるほどに奥深さを感じます.今回もまた一つ勉強になりました.