The code below implements a class, CountingOStream, whose job is to count the number of characters written to it while also discarding those characters and performing no memory allocation. It would be used to measure the size of some data that is being considered for writing before actually writing it.
It is based on examples at:
None of them do precisely what I want, but the first is quite close, so I chose that as the basis of my attempt.
It appears to work properly based on the included testing.
Specific areas of concern:
Have I missed some important technical requirement when inheriting
std::streambufandstd::ostream?Should there be additional constructors? My class is closest in spirit to
std::ostringstream, which has a lot of constructors, but I've only ever used its default constructor, so that is all I defined in my class. Is there some important idiom for which only having the default will be a problem?Are there unforeseen issues, including potential performance problems, in overriding
overflowthis way? It almost seems too easy...My class is a
streambuf, albeit privately, in addition to being anostream. That raises the potential for ambiguities that would not be encountered when using some otherostreamderived class.I already hit one resulting ambiguity, since
int_typeis declared on both sides. That one is easy to deal with inside the class, but if someone asks forCountingOStream::int_typethey will hit the ambiguity. I could add a declaration ofCountingOStream::int_typeto cover that, but do not expect it to be an issue in practice since I'm unaware of stream clients needing to useint_type(or other types potentially similarly affected).There do not seem to be any operators (especially
operator<<) overloaded forstreambuf, but perhaps I've overlooked something that could cause aCountingOStreamto not be usable where astd::ostringstreamwould.
Known issues I am not concerned about
- It is my understanding that a
std::ofstreamopened in text mode on Windows will turn LF into CRLF and hence would produce a longer file than this counter reports. For now at least, that's fine.
// counting-ostream.cc // ostream that just counts the characters written to it. // Loosely based on answers found at: // // https://stackoverflow.com/questions/772355/how-to-inherit-from-stdostream // https://stackoverflow.com/questions/41377045/is-there-a-simple-way-to-get-the-number-of-characters-printed-in-c // https://stackoverflow.com/questions/27534955/c-get-number-of-characters-printed-when-using-ofstream // // with the first one providing the actual code starting point. // -------------------------- Implementation --------------------------- #include <cstddef> // size_t #include <iostream> // std::streambuf, std::ostream // Present the 'std::ostream' interface and count the number of // characters written to it. That is, the final count equals what one // would get by instead writing to a 'std::ostringstream' and then // asking for the length of the string it contains at the end. But this // class avoids all of the memory allocation that that would entail. // // 'streambuf' is inherited privately because we need to override one of // its virtual methods (so it cannot be a member) but 'CountingOStream' // is not meant to be treated as a 'streambuf' by clients. // class CountingOStream : private std::streambuf, public std::ostream { public: // data // Count of characters seen. size_t m_count; public: // methods CountingOStream() : std::streambuf(), std::ostream(this /*as a streambuf*/), m_count(0) {} private: // methods // Override the method to count and discard written characters. // // There is an 'int_type' coming from both superclasses, so pick the // one relevant to the overridden method. virtual std::streambuf::int_type overflow( std::streambuf::int_type c) override { ++m_count; // Anything other than Traits::eof() (usually -1) means success. return 0; } }; // ------------------------------- Test -------------------------------- #include <cassert> // assert #include <sstream> // std::ostringstream // Run a single test. 'writeOperands' is a chain of things to be // written using 'operator<<'. #define TEST_WITH(writeOperands) \ { \ CountingOStream cos; \ cos << writeOperands; \ \ std::ostringstream oss; \ oss << writeOperands; \ \ size_t actual = cos.m_count; \ size_t expect = oss.str().size(); \ \ /* Print each test case and flush before checking. */ \ std::cout \ << "actual=" << actual \ << " expect=" << expect \ << " string: " << oss.str() \ << std::endl; \ assert(actual == expect); \ } int main() { TEST_WITH("Hello, world!\n"); TEST_WITH("Look a number: " << std::hex << 29 << std::endl); TEST_WITH('\0'); TEST_WITH('\377'); // Larger test to exercise any buffering, resizing, etc. { CountingOStream cos; std::ostringstream oss; for (int i=0; i < 10000; ++i) { cos << "string" << 'x' << 123; oss << "string" << 'x' << 123; } size_t actual = cos.m_count; size_t expect = oss.str().size(); std::cout << "larger: actual=" << actual << " expect=" << expect << std::endl; assert(actual == expect); } return 0; } // EOF Output from running the test program:
actual=14 expect=14 string: Hello, world! actual=18 expect=18 string: Look a number: 1d actual=1 expect=1 string: actual=1 expect=1 string: <a replacement character> larger: actual=100000 expect=100000