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.