CPrintStream - Polymorphic Printf

Warning! Some information on this page is older than 6 years now. I keep it for reference, but it probably doesn't reflect my current knowledge and beliefs.

Thu
02
Feb 2012

Constructing and sending somewhere (like to the console or to file) a string created ("formatted") from multiple values of different types (strings, numbers) is a common task in every programming language. C implements it using printf function, which is very convenient, although not type safe. In C++ it is recommended to use streams from standard library, but they are slower, as well as much less pleasant to code comparing to the functions that use formatting string. Let's say you have a numeric variable and you want to print it as 8-digit hexadecimal value, like "0x00001234". Which option looks better?

#include <cstdlib>
#include <iostream>
#include <iomanip>

unsigned u = 0x1234;

// Option 1
printf("0x%08X\n", u);

// Option 2
std::cout << "0x" << std::setw(8) << std::setfill('0') << std::hex << u << std::endl;

I choose option 1 :) In the world of GUI applications, printing text messages may seem not so common, but sometimes it it useful, e.g. for logging debug information. Recently I thought: Why not connect the C-style printing with what is good in C++: classes and polymorphism? That's how I coded simple CPrintStream class and some derived classes, which I'd like to share here:

PrintStream.hpp
PrintStream.cpp

Abstract base class CPrintStream defines method printf(const char* format, ...) for printing a formatted message to some kind of target. Derived classes implemented this as printing to system console (CConsolePrintStream), file (CFilePrintStream) or a buffer in memory (CMemoryPrintStream).

Here are some implementation details. First problem I had to overcome was that the variable argument list ... cannot be passed from function to function. It has to be resolved to a value of type va_list using va_start and va_end functions. Fortunately this va_list object can be passed as argument to another function and all functions that take ... (like printf, fprintf) have their equivalents taking va_list (like vprintf, vfprintf). So my CPrintStream::printf method just converts ... to va_list and passes it to another method called vprintf, and only this one is pure virtual to be overriden in derived classes.

class CPrintStream {
public:
    virtual ~CPrintStream() = 0 { }
    void printf( const char* format, ... );
    virtual void vprintf( const char* format, va_list argList ) = 0;
};

CConsolePrintStream does its job using vprintf function from standard C library - the va_list equivalent of printf. CFilePrintStream uses fopen_s to open a file of type FILE* and does printing with vfprintf function - the va_list equivalent of fprintf.

Finally, the CMemoryPrintStream uses std::vector<char> as buffer for raw binary data. I believe that's the best choice as: 1. STL vector can be easily resized, 2. char is the type recommended to represent raw bytes, 3. vector stores data in continuous memory that you can safely access by writing &myVector[index], which is not guaranteed in std::string. How do I store formatted string in memory? I use vsprintf_s - the va_list equivalent of sprintf. But before this, I calculate required length with _vscprintf - a function I've learned about recently. I believe using it is much better than sprintf-ing to some buffer of constant length, e.g. char temp[1024], like many programmers do.

Update 2015-12-05: A newer version of this code is available on: GitHub sawickiap / MISC / PrintStream

Comments | #c++ Share

Comments

[Download] [Dropbox] [pub] [Mirror] [Privacy policy]
Copyright © 2004-2024