C++ Tutorial (5)

This is part 5 of a fast paced C++ tutorial for programmers familiar with high level languages like Perl and Python.

Beyond the C++ STL

In the previous C++ tutorials, all examples were restricted to the C++ Standard Templates Library (STL), which is part of every ANSI C++ compliant compiler environment, and which we can take for granted.

Unfortunately, the STL doesn’t include classes for many popular areas, like:

  • Networking
  • Crypto
  • Databases
  • XML
  • GUI Frameworks

This is intentional: because C++ is a superset of C, C++ programmers could just as easily call external C or C++ libraries (e.g. Berkeley Sockets API for networking, OpenSSL for Crypto, C bindings for SQLite3, PostgreSQL, MySQL, … for database connectivity, SAX and DOM for XML parsing, and various C++ GUI frameworks like Qt, wxWidgets, and so on.

C++ designers didn’t want to impose a default standard for all those areas of application: C++ and the STL’s philosophy is distinctly different from Java’s which includes and therefore standardizes a lot of different APIs.

So, as C++ programmers, we’re confronted with a series of choices regarding external libraries. Which library is best suited for networking? For database connectivity?…

As firm believers in Open Source Software (OSS), we eliminate closed-source and proprietary libraries right from the start (feel free to use one, if need be). Furthermore, we eliminate libraries that are not portable across platforms: it just doesn’t make sense to develop against a Windows-only API if you want to port your application to Linux later, or vice-versa, right?

There are many cross-platform OSS C++ libraries out there, some of them highly specialized, others broad in scope and size. The following “generalist” libraries are interesting from the point of view of a general application developer:

  • Boost: a collection of C++ libraries designed by many members of the C++ standards committee with the intent to include the best of them in revised versions of the C++ Standard.
  • POCO: a set of portable C++ components that aims to close many gaps left open by the STL.
  • Qt: A powerful, cross-platform framework of C++ classes for GUI development.

In all cases, before using an external library, it is necessary to download, compile and install it both on the development and on the target machine. In this tutorial, we’ll explore a couple of classes from the POCO library, so if it isn’t already installed on your system, you’ll need to fetch it from its web site, and install it.

Base64 encoding and decoding files with POCO

To transmit files over a channel that is not 8-bit clean (e.g. UUCP, old SMTP, NNTP etc…), it is necessary to encode binary files in such a way that only some characters are being used. A long time, ago, people used to uuencode(1) and uudecode(1) such files, but today, we would Base64-encode and -decode them.

On some systems (like FreeBSD), we can use the utilities b64encode and b64decode, that are already part of the system, to achieve the job. But on most other systems, we need to roll our own Base64 encoders and decoders.

Fortunately, POCO provides the classes Poco::Base64Encoder and Poco::Base64Decoder to do the job.

Base-64 encoding a file: b64encode.cpp

This is one possible implementation of b64encode using the Poco::Base64Encoder class:

// b64encode.cpp -- Base64 encode a file with Poco::Base64Encoder

#include <fstream>
#include <cstdlib>
#include "Poco/Base64Encoder.h"

int
main (int argc, char *argv[])
{
  std::ifstream ifs(argv[1]);
  std::ofstream ofs(argv[2]);
  Poco::Base64Encoder b64out(ofs);

  std::copy(std::istreambuf_iterator<char>(ifs),
            std::istreambuf_iterator<char>(),
            std::ostreambuf_iterator<char>(b64out));
  b64out.close(); // always call this at the end!

  return EXIT_SUCCESS;
}

This is how to compile this program:

% c++ -O2 -I/usr/local/include -Wall   -c -o b64encode.o b64encode.cpp
% cc -L/usr/local/lib  b64encode.o  -lPocoFoundation -o b64encode

The program is not really that much different from copy4.cpp of the previous tutorial, which was, in a nutshell:

std::ifstream ifs(argv[1]);
std::ofstream ofs(argv[2]);

std::copy(std::istreambuf_iterator<char>(ifs),
          std::istreambuf_iterator<char>(),
          std::ostreambuf_iterator<char>(ofs));

ifs.close();
ofs.close();

The only difference is that we copy the output to b64out, which wraps the std::ofstream ofs in a Poco::Base64Encoder, and use b64out as the destination of the std::copy operation.

This is the result of Base64-encoding some big file:

% ./b64encode /boot/kernel/kernel /var/tmp/kernel.b64

% ls -l /boot/kernel/kernel /var/tmp/kernel.b64 
-r-xr-xr-x  1 root   wheel  12161158 Feb 24 12:47 /boot/kernel/kernel
-rw-r--r--  1 farid  wheel  16665292 Mar 21 16:09 /var/tmp/kernel.b64

As you can see, the base-64 encoded file is, as expected, larger. We can also peek into (the beginning) of both files:

% hd /boot/kernel/kernel | head -5
00000000  7f 45 4c 46 02 01 01 09  00 00 00 00 00 00 00 00  |.ELF............|
00000010  02 00 3e 00 01 00 00 00  10 b1 18 80 ff ff ff ff  |..>.............|
00000020  40 00 00 00 00 00 00 00  08 4f 9f 00 00 00 00 00  |@........O......|
00000030  00 00 00 00 40 00 38 00  05 00 40 00 25 00 22 00  |....@.8...@.%.".|
00000040  06 00 00 00 05 00 00 00  40 00 00 00 00 00 00 00  |........@.......|

% hd /var/tmp/kernel.b64 | head -5
00000000  66 30 56 4d 52 67 49 42  41 51 6b 41 41 41 41 41  |f0VMRgIBAQkAAAAA|
00000010  41 41 41 41 41 41 49 41  50 67 41 42 41 41 41 41  |AAAAAAIAPgABAAAA|
00000020  45 4c 45 59 67 50 2f 2f  2f 2f 39 41 41 41 41 41  |ELEYgP////9AAAAA|
00000030  41 41 41 41 41 41 68 50  6e 77 41 41 41 41 41 41  |AAAAAAhPnwAAAAAA|
00000040  41 41 41 41 41 45 41 41  0d 0a 4f 41 41 46 41 45  |AAAAAEAA..OAAFAE|

We see that the second file contains only printable characters.

b64decode.cpp

The reverse operation is Base-64 decoding a file. Instead of Poco::Base64Encoder, we simply use a Poco::Base64Decoder, like this:

// b64decode.cpp -- Base64 decode a file with Poco::Base64Decoder

#include <fstream>
#include <cstdlib>
#include "Poco/Base64Decoder.h"

int
main (int argc, char *argv[])
{
  std::ifstream ifs(argv[1]);
  Poco::Base64Decoder b64in(ifs);
  std::ofstream ofs(argv[2]);

  std::copy(std::istreambuf_iterator<char>(b64in),
            std::istreambuf_iterator<char>(),
            std::ostreambuf_iterator<char>(ofs));

  return EXIT_SUCCESS;
}

This is, again, our file copy program, idiomatic version with std::copy and streambuf_iterators. In b64encode we wrapped ofs with Poco::Base64Encoder. Here, we wrapped ifs with Poco::Base64Decoder, resulting in an input stream b64in.

Compiling:

% c++ -O2 -I/usr/local/include -Wall   -c -o b64decode.o b64decode.cpp
% cc -L/usr/local/lib  b64decode.o  -lPocoFoundation -o b64decode

Now, let’s Base64-decode the file we’ve previously Base64-encoded:

% ./b64decode /var/tmp/kernel.b64 /var/tmp/kernel.decoded

% ls -l /boot/kernel/kernel /var/tmp/kernel.decoded 
-r-xr-xr-x  1 root   wheel  12161158 Feb 24 12:47 /boot/kernel/kernel
-rw-r--r--  1 farid  wheel  12161158 Mar 21 16:19 /var/tmp/kernel.decoded

% diff /boot/kernel/kernel /var/tmp/kernel.decoded
% rm /var/tmp/kernel.b64 /var/tmp/kernel.decoded

Of course, we’ve got the very same file that we’ve encoded previously.

Base64-encoding and -decoding strings: b64strings.cpp

Suppose we don’t want to Base-64 encode whole files, but only std::strings. One example could be that we want to compose Base64-encoded e-mail messages from some data that the user entered in a GUI element.

We could re-use Poco::Base64Encoder and Poco::Base64Decoder to transform strings, but there’s a little problem here: both classes need output- und input streams, respectively, and not strings! However, the signature of the functions we need are:

std::string toBase64 (const std::string &source);
std::string fromBase64 (const std::string &source);

Fortunately, we can easily transform a string to an input or output stream with std::istringstream and std::ostringstream from <sstream>. toBase64 could look like this:

std::string
toBase64 (const std::string &source)
{
  std::istringstream in(source);
  std::ostringstream out;
  Poco::Base64Encoder b64out(out);

  std::copy(std::istreambuf_iterator<char>(in),
            std::istreambuf_iterator<char>(),
            std::ostreambuf_iterator<char>(b64out));
  b64out.close(); // always call this at the end!

  return out.str();
}

and fromBase64 would be:

std::string
fromBase64 (const std::string &source)
{
  std::istringstream in(source);
  std::ostringstream out;
  Poco::Base64Decoder b64in(in);

  std::copy(std::istreambuf_iterator<char>(b64in),
            std::istreambuf_iterator<char>(),
            std::ostreambuf_iterator<char>(out));

  return out.str();
}

Here’s one possible main program:

// b64strings.cpp -- functions to Base64 encode and decode strings.

#include <string>
#include <sstream>
#include <algorithm>
#include <iterator>
#include <iostream>
#include <cstdlib>
#include "Poco/Base64Encoder.h"
#include "Poco/Base64Decoder.h"

std::string toBase64 (const std::string &source);   // As shown above
std::string fromBase64 (const std::string &source); // As shown above

int
main (int argc, char *argv[])
{
  std::string clearText("hello, world!");
  std::string b64Text(toBase64(clearText));
  std::string clearAgain(fromBase64(b64Text));

  std::cout << "Clear1: " << clearText  << std::endl;
  std::cout << "Base64: " << b64Text    << std::endl;
  std::cout << "Clear2: " << clearAgain << std::endl;

  return EXIT_SUCCESS;
}

Compling and running it:

% c++ -O2 -I/usr/local/include -Wall   -c -o b64strings.o b64strings.cpp
% cc -L/usr/local/lib  b64strings.o  -lPocoFoundation -o b64strings

% ./b64strings 
Clear1: hello, world!
Base64: aGVsbG8sIHdvcmxkIQ==
Clear2: hello, world!

Summary

To overcome the (intentional) limitations of the C++ STL, it is necessary to use external libraries. We distinguish between closed-source and open-source libraries, between highly specialized and broad scope libraries, and between platform-specific and cross-platform libraries.

Good libraries include Boost, Poco, and Qt, but they are by no means the only ones. C++ isn’t Java: the standard doesn’t define what external libraries are best suited for your needs. The choice is yours to make.

As an example, we’ve used the input stream adapter Poco::Base64Encode from the POCO library to Base64-encode files (or streams, more generally), and Poco::Base64Decode to Base64-decode files (or streams). We’ve seen how to make use of std::istringstream and std::ostringstring in combination with the above mentioned POCO classes, to Base64-encode and Base64-decode std::strings.

Basically, we’re simply plumbing well-tested code components together and don’t reinvent the wheel.