Skip to content

Instantly share code, notes, and snippets.

@siritori
Created October 6, 2019 22:09
Show Gist options
  • Save siritori/6fd6fa2c5414556dd153f3f2a80c73da to your computer and use it in GitHub Desktop.
Save siritori/6fd6fa2c5414556dd153f3f2a80c73da to your computer and use it in GitHub Desktop.
std::basic_streambufを使って独自streamを作る
#pragma once
/**
* @brief コールバックストリームバッファ
* @note 出力をバッファリングし、書き込み用の関数をコールバックする
*/
class CallbackStreamBuffer : public std::basic_streambuf<char>
{
/**
* @brief 親クラス
*/
using Base = std::basic_streambuf<char>;
/**
* @brief トレイト
*/
using Traits = std::char_traits<char>;
/**
* @brief ポインタ
*/
using ptr = std::unique_ptr<CallbackStreamBuffer>;
public:
/**
* @brief 書き込みのコールバック関数
* @param[in] data 書き込みたいデータ
* @param[in] size 書き込みたいデータのサイズ
* @return 書き込みに成功したか
*/
using WriteCallback = std::function<bool(const char* data, size_t size)>;
/**
* @brief コンストラクタ
* @param[in] buffer バッファ(最低でもcapacity以上のサイズ)
* @param[in] capacity バッファリングするサイズ
* @param[in] write 書き込みコールバック
*/
CallbackStreamBuffer(
std::unique_ptr<char[]> &&buffer,
size_t capacity,
const WriteCallback write
)
: m_available(true)
, m_capacity(capacity)
, m_buffer(std::move(buffer))
, m_write(write)
{
resetBuffer();
}
/**
* @brief capacityぶんのバッファを確保してStreamBufferを生成する
* @param[in] capacity バッファリングするサイズ
* @param[in] write 書き込みコールバック
* @return StreamBufferインスタンス (メモリ確保に失敗した場合はnullptr)
*/
static CallbackStreamBuffer::ptr WithCapacity(size_t capacity, const WriteCallback write)
{
auto buffer = std::make_unique<char[]>(capacity);
if (buffer == nullptr) {
return nullptr;
}
return std::move(std::make_unique<CallbackStreamBuffer>(std::move(buffer), capacity, write));
}
/**
* @brief バッファが溢れたときの処理をする
* @param[in] ch 最後に入れたかった文字
* @retval Traits::eof() 失敗
* @retval それ以外 成功
*/
int overflow(int ch) override
{
if (!write(m_buffer.get(), m_capacity)) {
return Traits::eof();
}
m_buffer[0] = static_cast<char>(ch);
resetBuffer();
Base::pbump(1);
return 1;
}
/**
* @brief 複数バイトのデータを一気に書き込む
* @param[in] data 書き込みたいデータ
* @param[in] size 書き込みたいデータのサイズ
* @return 書き込みに成功した文字数
*/
std::streamsize xsputn(const char* data, std::streamsize count) override
{
if (!m_available) {
// すでに失敗している
return 0;
}
if (getBufferedSize() + count < m_capacity) {
// バッファに収まりきるなら、入れて終わり
memcpy(&m_buffer[getBufferedSize()], data, static_cast<size_t>(count));
Base::pbump(static_cast<int>(count));
return count;
}
// バッファに残っていたものと足し合わせて吐き出し、バッファを空にする
size_t remainingSize = m_capacity - getBufferedSize();
memcpy(&m_buffer[getBufferedSize()], data, remainingSize);
if (!write(m_buffer.get(), m_capacity)) {
return 0;
}
resetBuffer();
// 渡されてきたdataをすべて処理するまでループ
size_t pos = remainingSize;
while (pos < count) {
const auto restSize = static_cast<size_t>(count) - pos;
if (restSize >= m_capacity) {
// バッファのキャパシティを超えるくらいのサイズがまだ残っているなら、そのままflush
if (!write(&data[pos], m_capacity)) {
return 0;
}
pos += m_capacity;
} else {
// 超えないサイズなら、バッファに貯めて終わりにする
memcpy(&m_buffer[0], &data[pos], restSize);
Base::pbump(static_cast<int>(restSize));
pos += restSize;
}
}
return count;
}
/**
* @brief ストレージデバイスと同期する
* @retval -1 失敗
* @retval 0 成功
*/
int flush()
{
if (!write(m_buffer.get(), getBufferedSize())) {
return -1;
}
resetBuffer();
return 0;
}
/**
* @brief バッファが利用可能か
* @return 利用可能か
*/
bool available() const
{
return m_available;
}
private:
size_t getBufferedSize() const
{
return Base::pptr() - Base::pbase();
}
void resetBuffer()
{
Base::setp(&m_buffer[0], &m_buffer[m_capacity]);
}
bool write(const char* data, size_t size)
{
if (m_available && !m_write(data, size)) {
m_available = false;
}
return m_available;
}
bool m_available;
const size_t m_capacity;
std::unique_ptr<char[]> m_buffer;
const WriteCallback m_write;
};
#include <iostream>
#include <memory>
#include <functional>
#include "CallbackStreamBuffer.cpp"
int main()
{
auto buffer = CallbackStreamBuffer::WithCapacity(11, [&](const char* buffer, size_t size) -> bool {
printf("dump!!!!!!!!!!: ");
for (size_t i = 0; i < size; i++) {
putchar(buffer[i]);
}
puts("");
return (++count) < 30;
});
std::ostream stream(buffer.get());
stream << 0;
stream << 123;
stream << 4;
stream << 5;
stream << "67" << 6666;
stream << 89;
stream << 12345;
stream << "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
stream.flush();
stream << 0;
stream << 123;
stream << 4;
stream << 5;
stream << "67" << 5555;
stream << 89;
stream << 12345;
stream << "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
stream.flush();
buffer->flush();
return 0;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment