From a4509273ce7e9952cdf208e0d5340bbe4a91b9d8 Mon Sep 17 00:00:00 2001 From: Sage Weil Date: Fri, 23 Mar 2012 06:02:48 -0700 Subject: [PATCH] common: add PrebufferedStreambuf Simple streambuf that uses a preallocated buffer, and then spills over into a std::string if necessary. Signed-off-by: Sage Weil --- src/Makefile.am | 5 ++ src/common/PrebufferedStreambuf.cc | 63 ++++++++++++++++++ src/common/PrebufferedStreambuf.h | 42 ++++++++++++ src/test/test_prebufferedstreambuf.cc | 95 +++++++++++++++++++++++++++ 4 files changed, 205 insertions(+) create mode 100644 src/common/PrebufferedStreambuf.cc create mode 100644 src/common/PrebufferedStreambuf.h create mode 100644 src/test/test_prebufferedstreambuf.cc diff --git a/src/Makefile.am b/src/Makefile.am index 622c7c1a7a164..308cecbb58507 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -489,6 +489,11 @@ unittest_addrs_CXXFLAGS = ${AM_CXXFLAGS} ${UNITTEST_CXXFLAGS} unittest_addrs_LDADD = libglobal.la $(PTHREAD_LIBS) -lm ${UNITTEST_LDADD} $(CRYPTO_LIBS) $(EXTRALIBS) check_PROGRAMS += unittest_addrs +unittest_prebufferedstreambuf_SOURCES = test/test_prebufferedstreambuf.cc common/PrebufferedStreambuf.cc +unittest_prebufferedstreambuf_CXXFLAGS = ${AM_CXXFLAGS} ${UNITTEST_CXXFLAGS} +unittest_prebufferedstreambuf_LDADD = ${UNITTEST_LDADD} $(EXTRALIBS) +check_PROGRAMS += unittest_prebufferedstreambuf + unittest_str_list_SOURCES = test/test_str_list.cc unittest_str_list_CXXFLAGS = ${AM_CXXFLAGS} ${UNITTEST_CXXFLAGS} unittest_str_list_LDADD = libglobal.la $(PTHREAD_LIBS) -lm ${UNITTEST_LDADD} $(CRYPTO_LIBS) $(EXTRALIBS) diff --git a/src/common/PrebufferedStreambuf.cc b/src/common/PrebufferedStreambuf.cc new file mode 100644 index 0000000000000..957f905bfe4cf --- /dev/null +++ b/src/common/PrebufferedStreambuf.cc @@ -0,0 +1,63 @@ + +#include "common/PrebufferedStreambuf.h" + +PrebufferedStreambuf::PrebufferedStreambuf(char *buf, size_t len) + : m_buf(buf), m_buf_len(len) +{ + // init output buffer + this->setp(m_buf, m_buf + m_buf_len); + + // so we underflow on first read + this->setg(0, 0, 0); +} + +PrebufferedStreambuf::int_type PrebufferedStreambuf::overflow(int_type c) +{ + int old_len = m_overflow.size(); + if (old_len == 0) { + m_overflow.resize(m_buf_len); + } else { + m_overflow.resize(old_len * 2); + } + m_overflow[old_len] = c; + this->setp(&m_overflow[old_len + 1], &*m_overflow.end()); + return std::char_traits::not_eof(c); +} + +PrebufferedStreambuf::int_type PrebufferedStreambuf::underflow() +{ + if (this->gptr() == 0) { + // first read; start with the static buffer + if (m_overflow.size()) + // there is overflow, so start with entire prealloc buffer + this->setg(m_buf, m_buf, m_buf + m_buf_len); + else if (this->pptr() == m_buf) + // m_buf is empty + return traits_ty::eof(); // no data + else + // set up portion of m_buf we've filled + this->setg(m_buf, m_buf, this->pptr()); + return *this->gptr(); + } + if (this->gptr() == m_buf + m_buf_len && m_overflow.size()) { + // at end of m_buf; continue with the overflow string + this->setg(&m_overflow[0], &m_overflow[0], this->pptr()); + return *this->gptr(); + } + + // otherwise we must be at the end (of m_buf and/or m_overflow) + return traits_ty::eof(); +} + +std::string PrebufferedStreambuf::get_str() const +{ + if (m_overflow.size()) { + std::string s(m_buf, m_buf_len); + s.append(&m_overflow[0], this->pptr() - &m_overflow[0]); + return s; + } else if (this->gptr() == m_buf) { + return std::string(); + } else { + return std::string(m_buf, this->gptr() - m_buf); + } +} diff --git a/src/common/PrebufferedStreambuf.h b/src/common/PrebufferedStreambuf.h new file mode 100644 index 0000000000000..9aa6fdd415353 --- /dev/null +++ b/src/common/PrebufferedStreambuf.h @@ -0,0 +1,42 @@ +#ifndef CEPH_COMMON_PREBUFFEREDSTREAMBUF_H +#define CEPH_COMMON_PREBUFFEREDSTREAMBUF_H + +#include +#include +#include + +/** + * streambuf using existing buffer, overflowing into a std::string + * + * A simple streambuf that uses a preallocated buffer for small + * strings, and overflows into a std::string when necessary. If the + * preallocated buffer size is chosen well, we can optimize for the + * common case and overflow to a slower heap-allocated buffer when + * necessary. + */ +class PrebufferedStreambuf + : public std::basic_streambuf::traits_type> +{ + char *m_buf; + size_t m_buf_len; + std::string m_overflow; + + typedef std::char_traits traits_ty; + typedef typename traits_ty::int_type int_type; + typedef typename traits_ty::pos_type pos_type; + typedef typename traits_ty::off_type off_type; + +public: + PrebufferedStreambuf(char *buf, size_t len); + + // called when the buffer fills up + int_type overflow(int_type c); + + // called when we read and need more data + int_type underflow(); + + /// return a string copy (inefficiently) + std::string get_str() const; +}; + +#endif diff --git a/src/test/test_prebufferedstreambuf.cc b/src/test/test_prebufferedstreambuf.cc new file mode 100644 index 0000000000000..489a985d8b430 --- /dev/null +++ b/src/test/test_prebufferedstreambuf.cc @@ -0,0 +1,95 @@ +// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*- +// vim: ts=8 sw=2 smarttab + +#include "common/PrebufferedStreambuf.h" +#include "gtest/gtest.h" + +TEST(PrebufferedStreambuf, Empty) +{ + char buf[10]; + PrebufferedStreambuf sb(buf, sizeof(buf)); + + std::istream is(&sb); + std::string out; + getline(is, out); + ASSERT_EQ("", out); +} + +TEST(PrebufferedStreambuf, Simple) +{ + char buf[10]; + PrebufferedStreambuf sb(buf, sizeof(buf)); + + std::ostream os(&sb); + os << "test"; + + std::istream is(&sb); + std::string out; + getline(is, out); + ASSERT_EQ("test", out); +} + +TEST(PrebufferedStreambuf, Multiline) +{ + char buf[10]; + PrebufferedStreambuf sb(buf, sizeof(buf)); + + std::ostream os(&sb); + const char *s = "this is a line\nanother line\nand a third\nwhee!\n"; + os << s; + + std::istream is(&sb); + std::string out; + getline(is, out, is.widen(0)); + ASSERT_EQ(s, out); +} + +TEST(PrebufferedStreambuf, Withnull) +{ + char buf[10]; + PrebufferedStreambuf sb(buf, sizeof(buf)); + + std::ostream os(&sb); + std::string s("null \0 and more", 15); + os << s; + + std::istream is(&sb); + std::string out; + getline(is, out); + ASSERT_EQ(s, out); +} + +TEST(PrebufferedStreambuf, SimpleOverflow) +{ + char buf[10]; + PrebufferedStreambuf sb(buf, sizeof(buf)); + + std::ostream os(&sb); + const char *s = "hello, this is longer than buf[10]"; + os << s; + + ASSERT_EQ(s, sb.get_str()); + + std::istream is(&sb); + std::string out; + getline(is, out); + ASSERT_EQ(s, out); +} + +TEST(PrebufferedStreambuf, ManyOverflow) +{ + char buf[10]; + PrebufferedStreambuf sb(buf, sizeof(buf)); + + std::ostream os(&sb); + const char *s = "hello, this way way way way way way way way way way way way way way way way way way way way way way way way way _way_ longer than buf[10]"; + os << s; + + ASSERT_EQ(s, sb.get_str()); + + std::istream is(&sb); + std::string out; + getline(is, out); + ASSERT_EQ(s, out); +} + -- 2.39.5