On 2014-11-27 I gave a talk about C++ and new features introduced with C++11: these are the examples. They are all licensed under the wtfpli version 2. See C++11 talk notes for the talk notes.
Note that the wrapper interfaces turns errors from the underlying libraries into exceptions, so the method calls just do what they should, without the need of documenting special return values for error messages, and removing the need for each library to implement yet another way of reporting errors.
Also note that all wrapper objects do RAII: you create them and they clean after themselves when they go out of scope.
The wrapper objects also have cast operators to make them behave as the pointer or handle that they are wrapping, so that they can be transparently passed to the underlying libraries.
(note: I had to add U+2063 INVISIBLE SEPARATOR
to prevent noreturn
statements to be misinterpreted by the blog formatter. If you copypaste the
code and encounter issues, you may want to delete the noreturn
statements and
retype them)
A gcrypt hash class
This class is a light wrapper around gcrypt's hashing functions.
ezhash.h
#ifndef EZHASH_H
#define EZHASH_H
#include <string>
#include <gcrypt.h>
namespace ezhash {
class Hash
{
protected:
// members can now be initialized just like this, without needing to repeat
// their default assignment in every constructor
gcry_md_hd_t handle = nullptr;
public:
Hash(int algo, unsigned int flags=0);
~Hash();
// Assign 'delete' to a method to tell the compiler not to generate it
// automatically. In this case, we make the object non-copiable.
Hash(const Hash&) = delete;
Hash(const Hash&&) = delete;
Hash& operator=(const Hash&) = delete;
// Add a buffer to the hash
void hash_buf(const std::string& buf);
// Add the contents of a file to the hash
void hash_file(int fd);
// Get a string with the hexadecimal hash
std::string read_hex(int algo=0);
/// Pretend that we are a gcry_md_hd_t handle
operator gcry_md_hd_t() { return handle; }
};
}
#endif
ezhash.cpp
#include "ezhash.h"
#include <unistd.h>
#include <errno.h>
#include <string>
#include <cstring>
#include <sstream>
#include <iomanip>
#include <stdexcept>
using namespace std;
namespace ezhash {
namespace {
// noreturn attribute, to tell the compiler that this function never returns
[[noreturn]] void throw_gcrypt_error(gcry_error_t err)
{
string msg;
msg += gcry_strsource(err);
msg += "/";
msg += gcry_strerror(err);
throw runtime_error(msg);
}
string errno_str(int error)
{
char buf[256];
#if (_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600) && ! _GNU_SOURCE
strerror_r(errno, buf, 256);
string res(buf);
#else
string res(strerror_r(errno, buf, 256));
#endif
return res;
}
[[noreturn]] void throw_libc_error(int error)
{
throw runtime_error(errno_str(error));
}
}
Hash::Hash(int algo, unsigned int flags)
{
gcry_error_t err = gcry_md_open(&handle, algo, flags);
if (err) throw_gcrypt_error(err);
}
Hash::~Hash()
{
gcry_md_close(handle);
}
void Hash::hash_buf(const std::string& buf)
{
gcry_md_write(handle, buf.data(), buf.size());
}
void Hash::hash_file(int fd)
{
char buf[4096];
while (true)
{
ssize_t res = ::read(fd, buf, 4096);
if (res < 0) ezfs::throw_libc_error();
if (res == 0) break;
gcry_md_write(handle, buf, res);
}
}
std::string Hash::read_hex(int algo)
{
unsigned char* res = gcry_md_read(handle, algo);
unsigned int len = gcry_md_get_algo_dlen(
algo == 0 ? gcry_md_get_algo(handle) : algo);
// Format the hash into a hex digit
stringstream hexbuf;
hexbuf << hex << setfill('0');
for (unsigned i = 0; i < len; ++i)
hexbuf << setw(2) << (unsigned)res[i];
return hexbuf.str();
}
}
Example usage
ezhash::Hash sha256(GCRY_MD_SHA256);
sha256.hash_buf("ciao\n");
sha256.hash_buf("foo\n");
cout << sha256.read_hex() << endl;
Simple sqlite bindings
Remarkably simple sqlite3 bindings based on lambda callbacks.
ezsqlite.h
#ifndef EZSQLITE_H
#define EZSQLITE_H
#include <sqlite3.h>
#include <string>
#include <functional>
#include <stdexcept>
namespace ezsqlite {
/// RAII wrapper around a sqlite3 database handle
class DB
{
protected:
sqlite3* handle = nullptr;
public:
// Open a connection to a SQLite database
DB(const std::string& filename, int flags=SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE);
DB(const DB&) = delete;
DB(const DB&&) = delete;
DB& operator=(const DB&) = delete;
~DB();
/**
* Execute a query, optionally calling 'callback' on every result row
*
* The arguments to callback are:
* 1. number of columns
* 2. text values of the columns
* 3. names of the columns
*/
// std::function can be used to wrap any callable thing in C++
// see: http://en.cppreference.com/w/cpp/utility/functional/function
void exec(const std::string& query, std::function<bool(int, char**, char**)> callback=nullptr);
/// Pretend that we are a sqlite3 pointer
operator sqlite3*() { return handle; }
};
}
#endif
ezsqlite.cpp
#include "ezsqlite.h"
namespace ezsqlite {
DB::DB(const std::string& filename, int flags)
{
int res = sqlite3_open_v2(filename.c_str(), &handle, flags, nullptr);
if (res != SQLITE_OK)
{
// From http://www.sqlite.org/c3ref/open.html
// Whether or not an error occurs when it is opened, resources
// associated with the database connection handle should be
// released by passing it to sqlite3_close() when it is no longer
// required.
std::string errmsg(sqlite3_errmsg(handle));
sqlite3_close(handle);
throw std::runtime_error(errmsg);
}
}
DB::~DB()
{
sqlite3_close(handle);
}
namespace {
// Adapter to have sqlite3_exec call a std::function
int exec_callback(void* data, int columns, char** values, char** names)
{
std::function<bool(int, char**, char**)> cb = *static_cast<std::function<bool(int, char**, char**)>*>(data);
return cb(columns, values, names);
}
}
void DB::exec(const std::string& query, std::function<bool(int, char**, char**)> callback)
{
char* errmsg;
void* cb = callback ? &callback : nullptr;
int res = sqlite3_exec(handle, query.c_str(), exec_callback, cb, &errmsg);
if (res != SQLITE_OK && errmsg)
{
// http://www.sqlite.org/c3ref/exec.html
//
// If the 5th parameter to sqlite3_exec() is not NULL then any error
// message is written into memory obtained from sqlite3_malloc() and
// passed back through the 5th parameter. To avoid memory leaks, the
// application should invoke sqlite3_free() on error message strings
// returned through the 5th parameter of of sqlite3_exec() after the
// error message string is no longer needed.
std::string msg(errmsg);
sqlite3_free(errmsg);
throw std::runtime_error(errmsg);
}
}
}
Example usage
// Connect to the database
ezsqlite::DB db("erlug.sqlite");
// Make sure we have a table
db.exec(R"(
CREATE TABLE IF NOT EXISTS files (
name TEXT NOT NULL,
sha256sum TEXT NOT NULL
)
)");
// Read the list of files that we know
map<string, string> files;
db.exec("SELECT name, sha256sum FROM files", [&](int columns, char** vals, char** names) {
if (columns != 2) return false;
files.insert(make_pair(vals[0], vals[1]));
return true;
});
A fast Directory object
This is a lightweight wrapper around O_PATH
file descriptors for directories.
I'd love to see a library of well-maintained and thin C++ bindings around libc,
that do little more than turning errors into exceptions and making it also work
with std::string
buffers.
ezfs.h
#ifndef EZFS_H
#define EZFS_H
#include <string>
#include <functional>
#include <memory>
#include <cerrno>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <dirent.h>
namespace ezfs {
class Directory
{
protected:
int handle = -1;
public:
Directory(const std::string& pathname, int flags=0);
~Directory();
Directory(const Directory&) = delete;
Directory(const Directory&&) = delete;
Directory& operator=(const Directory&) = delete;
/// List the directory contents
void ls(std::function<void(const dirent&)> callback);
int open(const std::string& relpath, int flags, mode_t mode=0777);
};
std::string errno_str(int error=errno);
[[noreturn]] void throw_libc_error(int error=errno);
}
#endif
ezfs.cpp
#include "ezfs.h"
#include <stdexcept>
#include <memory>
#include <cstring>
#include <cstdlib>
#include <string>
#include <linux/limits.h>
using namespace std;
namespace ezfs {
string errno_str(int error)
{
char buf[256];
#if (_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600) && ! _GNU_SOURCE
strerror_r(errno, buf, 256);
string res(buf);
#else
string res(strerror_r(errno, buf, 256));
#endif
return res;
}
[[noreturn]] void throw_libc_error(int error)
{
throw runtime_error(errno_str(error));
}
Directory::Directory(const std::string& pathname, int flags)
{
handle = ::open(pathname.c_str(), O_PATH | O_DIRECTORY | flags);
if (handle < 0) throw_libc_error();
}
Directory::~Directory()
{
::close(handle);
}
void Directory::ls(std::function<void(const dirent&)> callback)
{
int fd = openat(handle, ".", O_DIRECTORY);
if (fd < 0) throw_libc_error();
// RAII Self-cleaning DIR object
unique_ptr<DIR, std::function<void(DIR*)>> dir(fdopendir(fd), [](DIR* dir) { if (dir) closedir(dir); });
if (!dir)
{
// fdopendir(3): After a successful call to fdopendir(), fd is used
// internally by the implementation, and should not otherwise be used
// by the application.
//
// but if the fdopendir call was not successful, fd is not managed by
// DIR, and we still need to close it, otherwise we leak a file
// descriptor.
//
// However, close() may modify errno, so we take note of the errno set
// by fdopendir and raise the exception based on that.
int fdopendir_errno = errno;
close(fd);
throw_libc_error(fdopendir_errno);
}
// Size the dirent buffer properly
const unsigned len = offsetof(dirent, d_name) + PATH_MAX + 1;
unique_ptr<dirent, std::function<void(void*)>> dirbuf((dirent*)malloc(len), free);
while (true)
{
dirent* res;
int err = readdir_r(dir.get(), dirbuf.get(), &res);
// End of directory contents
if (err == 0)
{
if (res)
callback(*res);
else
break;
} else
throw_libc_error(err);
}
}
int Directory::open(const std::string& relpath, int flags, mode_t mode)
{
int res = openat(handle, relpath.c_str(), flags, mode);
if (res < 0) throw_libc_error();
return res;
}
}
Example usage
// This is almost the equivalent of running "sha256sum ."
ezfs::Directory dir(".");
dir.ls([&](const dirent& d) {
if (d.d_type != DT_REG) return;
ezhash::Hash sha256(GCRY_MD_SHA256);
// I have no RAII wrapper around file handles at the moment, so
// I'll have to use a try/catch for cleaning up after errors
int fd = dir.open(d.d_name, O_RDONLY);
try {
sha256.hash_file(fd);
close(fd);
} catch (...) {
close(fd);
throw;
}
cout << sha256.read_hex() << " " << d.d_name << endl;
});