はじめに
同期ストリームとは、スレッドセーフに使えるストリームのことです。C++20 で追加されます。(2019/07/05 現在 gcc, clang 未実装です。)これを使うと、std::cout
などのストリームに、アトミックに出力することができます。また、ここでは入力はサポートされていません。
使い方
これを使うためのヘッダは<syncstream>
です。例を次に示します。
#include <iostream>
#include <syncstream>
#include <thread>
void thread1()
{
{
std::osyncstream bout{std::cout};
bout << "Hello, ";
bout << "World!";
bout << std::endl; // フラッシュがノートされます
bout << "and more!\n";
} // 文字が転送され、cout はフラッシュします
}
void thread2()
{
// 同じバッファに行われる出力は、異なる std::basic_osyncstream(std::basic_syncbuf) の
// インスタンスからでも、アトミックに出力されることが保証されます
std::osyncstream(std::cout) << "Goodbye, " << "Planet!" << '\n';
}
int main()
{
std::thread th1(thread1);
std::thread th2(thread2);
th1.join();
th2.join();
}
// 出力 :
// Hello, World!
// and more!
// Goodbye, Planet!
上記のように、std::osyncstream
のオブジェクトを、出力したいストリームで初期化するだけで OK です。また、同じストリームへの出力は、どのstd::osyncstream
オブジェクトからでもアトミックに出力できます。そのため、この実装はラップされたストリームのアドレスをキーとする static
なハッシュマップとしてロックを保持するようなものになるようです。
注意しなければならないことは、std::osyncstream
が文字をストリームへ転送するのは、メンバ関数emit()
が呼ばれたときだということです。メンバ関数emit()
は明示的に呼ぶこともできますが、std::osyncstream
オブジェクトのデストラクタが呼ばれる際に自動的に呼ばれるので、デストラクタに任せることもできます。上の例では、後者のデストラクタによるemit()
の呼び出しによって、文字を転送しています。
挙動のカスタマイズ
上記のような理由で、通常は、同期ストリームのオブジェクトにstd::endl
などを使用しても、直ちに文字が転送され表示されることはありません。ただし、std::endl
などによってstd::flush
が呼ばれる際に、自動的にemit()
を呼び出すようにすることができます。この例を、次に示します。
#include <iostream>
#include <syncstream>
int main()
{
std::osyncstream bout{std::cout};
bout << "Hello, World!";
bout << std::emit_on_flush; // ☆
bout << std::flush; // 通常はここで保留中の文字は転送されませんが、
// emit_on_flush を呼び出し、同期時排出ポリシーが true となっているため、
// ここで文字が転送されます。
}
☆の部分で、flush()
が呼ばれたときに、emit()
を呼び出すかどうかを表す内部フラグをtrue
にしています。同期時排出ポリシーとは、このフラグのことだと思われます。std::emit_on_flush
と対になるマニピュレータに、std::noemit_on_flush
があります。こちらは、同期時排出ポリシーをfalse
に設定します。(デフォルトでは、false
になっています。)
また、同期時排出ポリシーを変更しなくても、std::flush_emit
を上記の例のように使用することで、その時点で文字を転送し、ストリームをフラシュさせることができます。
まとめ
std::osyncstream
を使うことで、ストリームへアトミックに出力できます。ただし、文字はメンバ関数emit()
が呼ばれた際に転送されます。