読者です 読者をやめる 読者になる 読者になる

meryngii.neta

今日も新たな"ネタ"を求めて。

operatorでストリームの書式状態を保存する

C++

C++のストリームの書式状態は、一度変更すると再び変更しない限りそのままになる。書式フラグを元に戻すのを忘れていれば、同じストリームを使った別の部分のコードで思わぬ表示が出るかもしれない。
書式状態を保存するためのライブラリとしてboost::ios_stateというものがあるが、いちいちスコープが必要で、それぞれのクラス名も覚えなければならないのであまり使いやすくはない。
そこで、operator<と>をオーバーロードして、一文ごとにストリームの書式状態を復元する簡単なライブラリを作ってみた。これを使うとこのように書ける。

cout < hex < 256 < endl;

cout << 256 << endl;

すると次のように表示される。

100
256

普通にすべてシフト演算子で書けば、2つ目もhexが効いて16進数で表示されるはずだが、この例だと1つ目の文が終わると状態が復元されて10進数で表示される。
このライブラリのコードは次の通り。

#include <iostream>

template <class charT, class traits>
class saved_stream
{
public:
	explicit saved_stream(std::basic_ios<charT, traits>& stream)
		: stream_(stream), is_last_(false)
		, flags_(stream.flags()), fill_(stream.fill()), precision_(stream.precision()), width_(stream.width()) { }
	
	saved_stream(const saved_stream& saved)
		: stream_(saved.stream_), is_last_(true)
		, flags_(saved.flags_), fill_(saved.fill_), precision_(saved.precision_), width_(saved.width_)
	{
		saved.is_last_ = false;
	}
	
	~saved_stream()
	{
		if (is_last_)
		{
			stream_.flags(flags_);
			stream_.precision(precision_);
			stream_.width(width_);
			stream_.fill(fill_);
		}
	}
	
private:
	std::basic_ios<charT, traits>& stream_;
	const std::ios_base::fmtflags flags_;
	const std::streamsize precision_, width_;
	const charT fill_;
	mutable bool is_last_;
};

template <class charT, class traits>
class saved_ostream
	: public saved_stream<charT, traits>
{
public:
	explicit saved_ostream(std::basic_ostream<charT, traits>& stream)
		: saved_stream<charT, traits>(stream), stream_(stream) { }
	
private:
	std::basic_ostream<charT, traits>& stream_;
	
	template <class T>
	friend saved_ostream operator < (const saved_ostream& saved, T& value)
	{
		saved.stream_ << value;
		return saved_ostream(saved);
	}
	template <class T>
	friend saved_ostream operator < (const saved_ostream& saved, const T& value)
	{
		saved.stream_ << value;
		return saved_ostream(saved);
	}
	friend saved_ostream operator < (const saved_ostream& saved,
		std::basic_ostream<charT, traits>& (*value)(std::basic_ostream<charT, traits>&))
	{
		saved.stream_ << value;
		return saved_ostream(saved);
	}
	friend saved_ostream operator < (const saved_ostream& saved,
		std::basic_ios<charT, traits>& (*value)(std::basic_ios<charT, traits>&))
	{
		saved.stream_ << value;
		return saved_ostream(saved);
	}
};

template <class charT, class traits, class T>
saved_ostream<charT, traits> operator < (std::basic_ostream<charT, traits>& stream, T& value)
{
	return saved_ostream<charT, traits>(stream) < value;
}
template <class charT, class traits, class T>
saved_ostream<charT, traits> operator < (std::basic_ostream<charT, traits>& stream, const T& value)
{
	return saved_ostream<charT, traits>(stream) < value;
}
template <class charT, class traits>
saved_ostream<charT, traits> operator < (std::basic_ostream<charT, traits>& stream,
	std::basic_ostream<charT, traits>& (*value)(std::basic_ostream<charT, traits>&))
{
	return saved_ostream<charT, traits>(stream) < value;
}
template <class charT, class traits>
saved_ostream<charT, traits> operator < (std::basic_ostream<charT, traits>& stream,
	std::basic_ios<charT, traits>& (*value)(std::basic_ios<charT, traits>&))
{
	return saved_ostream<charT, traits>(stream) < value;
}

template <class charT, class traits>
class saved_istream
	: public saved_stream<charT, traits>
{
public:
	explicit saved_istream(std::basic_istream<charT, traits>& stream)
		: saved_stream<charT, traits>(stream), stream_(stream) { }
	
private:
	std::basic_istream<charT, traits>& stream_;
	
	template <class T>
	friend saved_istream operator > (const saved_istream& saved, T& value)
	{
		saved.stream_ >> value;
		return saved_istream(saved);
	}
	template <class T>
	friend saved_istream operator > (const saved_istream& saved, const T& value)
	{
		saved.stream_ >> value;
		return saved_istream(saved);
	}
	friend saved_istream operator > (const saved_istream& saved,
		std::basic_istream<charT, traits>& (*value)(std::basic_istream<charT, traits>&))
	{
		saved.stream_ >> value;
		return saved_istream(saved);
	}
	friend saved_istream operator > (const saved_istream& saved,
		std::basic_ios<charT, traits>& (*value)(std::basic_ios<charT, traits>&))
	{
		saved.stream_ >> value;
		return saved_istream(saved);
	}
};

template <class charT, class traits, class T>
saved_istream<charT, traits> operator > (std::basic_istream<charT, traits>& stream, T& value)
{
	return saved_istream<charT, traits>(stream) > value;
}
template <class charT, class traits, class T>
saved_istream<charT, traits> operator > (std::basic_istream<charT, traits>& stream, const T& value)
{
	return saved_istream<charT, traits>(stream) > value;
}
template <class charT, class traits>
saved_istream<charT, traits> operator > (std::basic_istream<charT, traits>& stream,
	std::basic_istream<charT, traits>& (*value)(std::basic_istream<charT, traits>&))
{
	return saved_istream<charT, traits>(stream) > value;
}
template <class charT, class traits>
saved_istream<charT, traits> operator > (std::basic_istream<charT, traits>& stream,
	std::basic_ios<charT, traits>& (*value)(std::basic_ios<charT, traits>&))
{
	return saved_istream<charT, traits>(stream) > value;
}

このコードは一時オブジェクトを大量に生成するが、最適化が効けば通常は省略されるので特に問題とはならない。
もちろんフラグ以外の状態も復元される。自作のマニピュレータも自由に使える。

#include <iomanip>
using namespace std;

/* ... */

cout < setprecision(2) < hex < setw(15)
	< scientific < setfill('0') < 1234.5678 < endl;

cout << 1234.5678 < endl;
0000001.23e+003
1234.57

私としてはなかなか使いやすいと思うのだが、演算子が紛らわしいかもしれない。operator <とoperator >の部分を置き換えれば簡単に改造することはできる。ぜひお試しあれ。