4/13/2012

SQLite OOP V2 (C++11)


Before finishing off the filelistener project, I want to share even another SQLite database class with you. The last last class is, while very easy to use, also very limited: it does not support variable escaping, and thus effectively does not allow for user input without risking some form of SQL injection. There are two reasons for this: firstly I did not need to escape any user input in the project I used it for, and secondly because I did not come up with a neat way to put it in without making the class a lot less intuitive to use.

Well, for those of you that can make usage of C++11 code, here is a new and header-only version of the SQLite database class that supports parameter escaping and has a few other improvements without losing simplicity:


/*
    sqlite3 database class
    author: Jan Nikolas Jansen
    date: 2012-04-13
    
    written in c++11, tested with g++4.6.3 using -std=c++0x
    link with -lsqlite3
*/

#ifndef database_HPP
#define database_HPP

// sqlite3 C API header
#include <sqlite3.h>

// boost optional library for handling NULL values in database columns
#include <boost/optional.hpp>

// STL containers used
#include <string>
#include <map>
#include <vector>
#include <sstream>

/**
 * sqlite3 database class.
 * @author nijansen
 *
 * Enhanced C++11 version of the sqlite3 database management class published
 * in my blog earlier. Supports escaping most values to protect against SQL
 * injections by making use of variadic templates. Enables foreign key support
 * by default.
 *
 * Since this class is heavy in templates and thus cannot be neatly divided into
 * a header and source file anyway, it is published as header only.
 */
class database {
public:
    /**
     * Connects to a database.
     *
     * If the database file does not exist, it will be created if possible and
     * then opened. Foreign key support will be enabled. If everything was
     * successful, the class will boolcast to true. If something went wrong, the
     * error function provides more details.
     *
     * @param[in] filename Location of the database file.
     */
    explicit database(const std::string &filename)
        : connected_(false)
    {
        if(sqlite3_open(filename.data(), &database_) == SQLITE_OK
            && sql("PRAGMA foreign_keys = ON;"))
        {
            connected_ = true;
        }
    }

    /**
     * Closes the connection to the database.
     */
    virtual ~database() { sqlite3_close(database_); }

    /**
     * Checks for correct initialization.
     *
     * @return true, if successful. Check the error function otherwise.
     */
    explicit operator bool() const { return connected_; }

    /** Definition of a database table row. **/
    typedef std::map<std::string, boost::optional<std::string>> Row;

    /**
     * SQL Query, non-responsive version.
     *
     * Use this version if you execute statements that do not return a result
     * (like INSERT, UPDATE, etc.). You can use escaped parameters in your query
     * by writing a ? in the string instead of the desired value and appending
     * the values after the string in chronologic order.
     *
     * @param[in] query SQL statement, possibly with placeholders.
     * @param[in] args Argument list of values that need to be escaped.
     * @return true, if successful. Check the error function otherwise.
     */
    template <typename... Args> bool sql(const std::string &query,
        const Args &... args)
    {
        sql_statement call;
        if(sqlite3_prepare_v2(database_, query.data(), -1, &call.statement,
            nullptr) != SQLITE_OK)
        {
            return false;
        }
        if(!bind(call.statement, 1, args...)) {
            return false;
        }
        return sqlite3_step(call.statement) == SQLITE_DONE;
    }

    /**
     * SQL Query, responsive version.
     *
     * Use this version if you execute statements that do return a result (like
     * SELECT). You can use escaped parameters in your query by writing a ? in 
     * the string instead of the desired value and appending the values after 
     * the string in chronologic order.
     *
     * @param[in] query SQL statement, possibly with placeholders.
     * @param[in] rows Container for result that will be modified on success.
     * @param[in] args Argument list of values that need to be escaped.
     * @return true, if successful. Check the error function otherwise.
     */
    template <typename... Args> bool sql(const std::string &query,
        std::vector<Row> &rows, const Args &... args)
    {
        sql_statement call;
        if(sqlite3_prepare_v2(database_, query.data(), -1, &call.statement,
            nullptr) != SQLITE_OK)
        {
            return false;
        }
        if(!bind(call.statement, 1, args...)) {
            return false;
        }
        return get_rows(call.statement, rows);
    }

    /**
     * Is safe to call even when no error has occured yet.
     *
     * @return Last error message.
     */
    const std::string error() const {
        return sqlite3_errmsg(database_);
    }
private:
    /** sqlite3 statement wrapper for finalization **/
    struct sql_statement {
        sqlite3_stmt *statement;
        sql_statement() : statement(nullptr) {}
        ~sql_statement() { sqlite3_finalize(statement); }
    };

    /** bind dummy function for empty argument lists **/
    static bool bind(sqlite3_stmt *, const int) { return true; }

    /** bind delegator function that will call a specialized bind_struct **/
    template <typename T, typename... Args>
    static bool bind(sqlite3_stmt *statement,
        const int current, const T &first, const Args &... args)
    {
        return bind_struct<T, Args...>::f(statement, current,
            first, args...);
    }

    /** most general bind_struct that relies on implicit string conversion **/
    template <typename T, typename... Args>
    struct bind_struct {
        static bool f(sqlite3_stmt *statement, int current,
            const T &first, const Args &... args)
        {
            std::stringstream ss;
            ss << first;
            if(sqlite3_bind_text(statement, current,
                ss.str().data(), ss.str().length(),
                SQLITE_TRANSIENT) != SQLITE_OK)
            {
                return false;
            }
            return bind(statement, current+1, args...);
        }
    };

    /** bind_struct for double values **/
    template <typename... Args>
    struct bind_struct<double, Args...> {
        static bool f(sqlite3_stmt *statement, int current,
            double first, const Args &... args)
        {
            if(sqlite3_bind_double(statement, current, first)
                != SQLITE_OK)
            {
                return false;
            }
            return bind(statement, current+1, args...);
        }
    };

    /** bind_struct for int values **/
    template <typename... Args>
    struct bind_struct<int, Args...> {
        static bool f(sqlite3_stmt *statement, int current,
            int first, const Args &... args)
        {
            if(sqlite3_bind_int(statement, current, first)
                != SQLITE_OK)
            {
                return false;
            }
            return bind(statement, current+1, args...);
        }
    };

    /** bind_struct for byte arrays **/
    template <typename... Args>
    struct bind_struct<std::vector<char>, Args...> {
        static bool f(sqlite3_stmt *statement, int current,
            const std::vector<char> &first, const Args &... args)
        {
            if(sqlite3_bind_blob(statement, current,
                &first[0], first.size(),
                SQLITE_TRANSIENT) != SQLITE_OK)
            {
                return false;
            }
            return bind(statement, current+1, args...);
        }
    };

    /** retrieves rows from a prepared and bound statement **/
    bool get_rows(sqlite3_stmt *statement, std::vector<Row> &rows) {
        int retval = 0;
        do {
            retval = sqlite3_step(statement);
            if(retval != SQLITE_ROW && retval != SQLITE_DONE) {
                return false;
            }
            if(retval == SQLITE_ROW) {
                Row row;
                const auto ncols = sqlite3_column_count(statement);
                for(int i = 0; i < ncols; i++) {
                    const auto colname = sqlite3_column_name(statement, i);
                    const auto colbytes = sqlite3_column_text(statement, i);
                    const auto nbytes = sqlite3_column_bytes(statement, i);
                    if(colbytes) {
                        std::string colcontent;
                        colcontent.assign((char *)&colbytes[0], nbytes);
                        row[colname] = colcontent;
                    }
                    else {
                        row[colname] = boost::optional<std::string>();
                    }
                }
                rows.push_back(row);
            }
        } while(retval != SQLITE_DONE);
        return true;
    }

    /** true if initialized **/
    bool connected_;
    /** sqlite3 database handle **/
    sqlite3 *database_;
};

#endif // database_HPP



And here is a demonstration of how to use it:

#include "database.hpp"
#include <iostream>
#include <sstream>

int main() {
    database db("test.db");
    if(!db) {
        std::cerr << db.error() << std::endl;
        return 1;
    }
    std::stringstream query;
    query
        << "CREATE TABLE IF NOT EXISTS test ("
        << "    id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT, "
        << "    name VARCHAR[20] NOT NULL, "
        << "    age INTEGER"
        << ")";
    if(!db.sql(query.str())) {
        std::cerr << db.error() << std::endl;
        return 1;
    }

    if(!db.sql("INSERT INTO test (name, age) VALUES (?, ?)", "Nikolas", 23))
    {
        std::cerr << db.error() << std::endl;
        return 1;
    }

    if(!db.sql("INSERT INTO test (name) VALUES (?)", "Bob")) {
        std::cerr << db.error() << std::endl;
        return 1;
    }

    std::vector<database::Row> rows;
    if(!db.sql("SELECT name, age FROM test;", rows)) {
        std::cerr << db.error() << std::endl;
        return 1;
    }
    for(auto row = rows.begin(); row != rows.end(); row++) {
        for(auto column = row->begin(); column != row->end(); column++) {
            std::cout
                << "[" << column->first << "]\t"
                << (column->second ? *column->second : "N/A")
                << std::endl;
        }
    }

    return 0;
}

4/04/2012

filelistener [Part 2]

Looking at the last part of the filelistener project, the next thing for us to do is:
"Write a file listener that iterates through an input directory in a given interval and triggers a purely virtual function for someone else to override"
Here is the approach to implement these features, which I recommend saving as include/filelistener.hpp:

#ifndef FILELISTENER_HPP
#define FILELISTENER_HPP

/*
    Link with the following libraries:
    -lboost_filesystem
    -lpthread
*/

#include "datastructures.hpp"
#include <boost/asio.hpp>
#include <boost/optional.hpp>
#include <boost/filesystem/operations.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>

/**
 * Listens on a directory and triggers on parsed files.
 *
 * @author nijansen
 *
 * The listener will monitor the input directory in a given interval. All files within
 * the input directory will be read; if they can be parsed, the listener will call a
 * purely virtual trigger function and pass the parsed information.
 *
 * The listener is not recursive: Directories within the input directory will be
 * ignored. If files cannot be parsed, the listener will move them into an "ignored"
 * directory within the input directory.
 *
 * The listener will not throw exceptions on parsing failures. It will throw if it
 * lacks the necessary rights to read/write within the input directory. Exceptions
 * within the purely virtual trigger function must be caught by the derived class; the
 * listener will NOT catch those. This is intended to prevent unexpected behavior.
 *
 * The listener itself is thread safe. It is strongly recommended to use one io
 * service for each thread. Several listener objects may be registered to one io
 * service if they are in the same thread.
 *
 * @warning Do not set up multiple listener objects on the same input directory!
 */
class FileListener {
public:
    /**
     * Constructor that registers to an Boost Asio io service.
     *
     * The derived class must overload this constructor and call the parent constructor
     * with a valid io service.
     *
     * @param[in] io_service Asio io service the listener will register to.
     */
    FileListener(boost::asio::io_service &io_service);

    /**
     * Sets the input directory.
     *
     * @param[in] input Must be a valid (existing) directory.
     * @return true if successful, false if invalid.
     */
    bool set_input_directory(const boost::filesystem::path &input);

    /**
     * Sets the listening interval.
     *
     * @param[in] interval_ms Listening interval in milliseconds.
     */
    void set_listening_interval(long interval_ms);

    /**
     * Tries to start the listener service.
     *
     * @return true if successful.
     *
     * @warning The input directory and listening interval must be set!
     * @warning You must run the io service after this call!
     */
    bool start_service();

    /**
     * Purely virtual trigger function for derived classes.
     *
     * Derived classes must implement this function. It will be called by the
     * listener when a file was parsed successfully. The return value determines
     * whether the file will be deleted.
     *
     * @param[in] tournament Parsed data from the file.
     * @return 0 if successful (file is deleted!), any other value if failure.
     *
     * @warning On success return value, the file will be deleted permanently!
     */
    virtual int trigger(const datastructures::Tournament &tournament) = 0;
private:
    /**
     * Iterates through the input directory, and triggers on parsed files.
     */
    void listen();

    /** Input directory; default: unset. **/
    boost::optional<boost::filesystem::path> input_;
    /** Listening interval; default: unset. **/
    boost::optional<boost::posix_time::time_duration> interval_;
    /** Timer to listen in intervals. **/
    boost::asio::deadline_timer timer_;
};

#endif // FILELISTENER_HPP


And here is the respective implementation source/filelistener.cpp:

#include "filelistener.hpp"
#include <boost/bind.hpp>

FileListener::FileListener(boost::asio::io_service &io_service)
    : timer_(io_service)
{}

bool FileListener::set_input_directory(const boost::filesystem::path &input) {
    // Only directories are valid input paths
    if(boost::filesystem::is_directory(input)) {
        input_ = input;
        return true;
    }
    return false;
}

void FileListener::set_listening_interval(long interval_ms) {
    interval_ = boost::posix_time::milliseconds(interval_ms);
}

bool FileListener::start_service() {
    // Only start the server if the input path and listening interval have been set
    if(input_ && interval_) {
        // Set the timer
        timer_.expires_from_now(*interval_);
        timer_.async_wait(boost::bind(&FileListener::listen, this));
        return true;
    }
    return false;
}

void FileListener::listen() {
    // Iterate through the input directory
    for(boost::filesystem::directory_iterator file(*input_);
        file != boost::filesystem::directory_iterator(); file++)
    {
        boost::filesystem::path current_file = file->path();
        // Ignore directories
        if(boost::filesystem::is_directory(current_file)) {
            continue;
        }
        // Try to parse the file
        datastructures::Tournament tournament;
        try {
            tournament.load(current_file.native());
        }
        catch(...) {
            // For performance reasons, move files that failed parsing into input/ignored,
            // so we will not try to parse them over and over again
            boost::filesystem::path ignored = *input_/"ignored";
            if(!boost::filesystem::exists(ignored)) {
                boost::filesystem::create_directory(ignored);
            }
            boost::filesystem::copy_file(current_file, ignored/current_file.filename());
            boost::filesystem::remove(current_file);
            continue;
        }
        // Call the trigger function of the derived class
        if(trigger(tournament) == 0) {
            // Only delete the file if the trigger function submitted the success value;
            // the file will trigger again later otherwise
            boost::filesystem::remove(current_file);
        }
    }
    // Reset the timer
    timer_.expires_from_now(*interval_);
    timer_.async_wait(boost::bind(&FileListener::listen, this));
}


This leaves us with only one more step for this project: an example implementation. Coming up in a future post, of course.

3/28/2012

SQLite OOP


Before continuing with the FileListener project, I want to share a class with you. I am currently working on a project that is heavy into storing data, so I wanted to use some database. Yet I do not want to force the user to set up a certain database framework just for this project. Because of this, I finally chose to use SQLite. SQLite comes with an C API, but I was really missing the comfort of object oriented structures. I ended up writing my own class for a very simple database management that suffices my needs. It does not have a lot (in fact, nearly nothing) to do with the Boost libraries this blog is dedicated to, but if someone else is looking for an object oriented approach for SQLite, this might come in handy, so I will share it anyways:

database.hpp:


#ifndef DATABASE_HPP
#define DATABASE_HPP

#include <sqlite3.h>
#include <boost/optional.hpp>
#include <string>
#include <map>
#include <vector>

class Database {
public:
    Database() : connected_(false) {}
    bool connect(const std::string &filename) throw();
    virtual ~Database();

    typedef std::map<std::string, boost::optional<std::string> > Row;
    bool sql_query(const std::string &query, std::vector<Row> *rows=0) throw();

    std::string error() const throw();
private:
    bool connected_;
    sqlite3 *database_;
};

#endif // DATABASE_HPP

database.cpp:


#include "database.hpp"

bool Database::connect(const std::string &filename) throw() {
    if(connected_) {
        sqlite3_close(database_);
        connected_ = false;
    }
    if(sqlite3_open(filename.data(), &database_) == SQLITE_OK) {
        connected_ = true;
        return true;
    }
    return false;
}

Database::~Database() {
    sqlite3_close(database_);
}

bool Database::sql_query(const std::string &query, std::vector<Row> *rows) throw() {
    struct call_struct {
        sqlite3_stmt *statement;
        call_struct() : statement(0) {}
        ~call_struct() { sqlite3_finalize(statement); }
    } call;

    if(sqlite3_prepare(database_, query.data(), -1, &call.statement, 0) != SQLITE_OK) {
        return false;
    }

    int retval;
    do {
        retval = sqlite3_step(call.statement);
        if(retval != SQLITE_ROW && retval != SQLITE_DONE) {
            return false;
        }
        if(retval == SQLITE_ROW) {
            Row row;
            const int ncols = sqlite3_column_count(call.statement);
            for(int i = 0; i < ncols; i++) {
                const std::string colname = sqlite3_column_name(call.statement, i);
                const unsigned char *colbytes = sqlite3_column_text(call.statement, i);
                const int nbytes = sqlite3_column_bytes(call.statement, i);
                if(colbytes) {
                    std::string colcontent;
                    colcontent.assign((char*)&colbytes[0], nbytes);
                    row[colname] = colcontent;
                }
                else {
                    row[colname] = boost::optional<std::string>();
                }
            }
            if(rows) {
                rows->push_back(row);
            }
        }
    } while(retval != SQLITE_DONE);

    return true;
}

std::string Database::error() const throw() {
    return sqlite3_errmsg(database_);
}

It is not the most powerful class out there, but it is very easy and straight forward to use. Here are some examples:

#include "database.hpp"
#include <iostream>
#include <sstream>

int main() {
    Database database;
    if(!database.connect("my_database.db")) {
        std::cerr << database.error() << std::endl;
        return 1;
    }
    std::stringstream query;
    query
        << "CREATE TABLE users ("
        << "id INTEGER PRIMARY KEY AUTOINCREMENT, "
        << "name VARCHAR[30] NOT NULL, "
        << "email VARCHAR[100]);";
    if(!database.sql_query(query.str())) {
        std::cerr << "Query failed: " << query.str() << std::endl
            << database.error() << std::endl;
        return 1;
    }
    return 0;
}

#include "database.hpp"
#include <iostream>
#include <sstream>

int main() {
    Database database;
    if(!database.connect("my_database.db")) {
        std::cerr << database.error() << std::endl;
        return 1;
    }
    std::stringstream query;
    query
        << "INSERT INTO users "
        << "(name, email) VALUES ('test', 'a@b.com');";
    if(!database.sql_query(query.str())) {
        std::cerr << "Query failed: " << query.str() << std::endl
            << database.error() << std::endl;
        return 1;
    }
    query.str(std::string());
    query
        << "INSERT INTO users "
        << "(name) VALUES ('dummy');";
    if(!database.sql_query(query.str())) {
        std::cerr << "Query failed: " << query.str() << std::endl
            << database.error() << std::endl;
        return 1;
    }
    return 0;
}

#include "database.hpp"
#include <iostream>
#include <sstream>

int main() {
    Database database;
    if(!database.connect("my_database.db")) {
        std::cerr << database.error() << std::endl;
        return 1;
    }
    std::stringstream query;
    query
        << "SELECT name, email FROM users;";
    std::vector<Database::Row> rows;
    if(!database.sql_query(query.str(), &rows)) {
        std::cerr << "Query failed: " << query.str() << std::endl
            << database.error() << std::endl;
        return 1;
    }
    int current_row = 1;
    for(std::vector<Database::Row>::const_iterator row = rows.begin();
        row != rows.end(); row++)
    {
        std::cout << "[" << current_row << "]" << std::endl;

        for(Database::Row::const_iterator column = row->begin();
            column != row->end(); column++)
        {
            std::cout << "\t"<< column->first << ": "
                << (column->second ? *column->second : "NULL")
                << std::endl;
        }
        current_row++;
    }

    return 0;
}

3/22/2012

filelistener [Part 1]

This time, as promised, we will look at a small project, rather than a snippet. Imagine the following context:

A customer is hosting a website organizing esport tournaments. He is providing us with xml files for each tournament, containing information about the tournament's name, its sponsors, the participating clans and their players, etc. Our job is to write a file listener, that will notice when a new xml file is saved in an input directory, try to parse this file, and sends triggers so someone else can implement something useful to do with the parsed information.

Our task will be split into these parts:
  1. Defining the datastructures used within the XML files and write a parser for these files
  2. Write a file listener that iterates through an input directory in a given interval and triggers a purely virtual function for someone else to override
  3. Construct a very simple demonstration of how to implement the purely virtual function and embed it into a fully functional program
Lets dive right into datastructures: we can store the information within structs. For example:

struct Sponsor {
    std::string name;
    std::string description;
    std::string url_website;
    std::string url_image;
};

However, we notice by looking through the example XML files provided by our customer that most values are optional, and may not be referenced at all. We will need to deal with these optional values somehow, and the from reading my last post, you already know one possible approach: pointers. While using pointers for optional values is a thoughtful approach, my first post was dedicated to explaining why large pointer structures are inconvenient to work with. There must be a better approach for this. Of course, Boost does have a library for this problem: Boost Optional. It is ridiculously easy to apply:

struct Sponsor {
    std::string name;
    boost::optional<std::string> description;
    boost::optional<std::string> url_website;
    boost::optional<std::string> url_image;
};

Handling these values feels naturally, using the dereference operator makes them feel a lot like the pointers used by gSOAP, but we can, for example, assign values to them directy, do not have to worry about allocating any memory for them, etc. Now we already have the structure in which we can store our information. What is missing is the parsing. Parsing is a rather complicated field, but, of course, Boost has it done for us already with the Boost Property Tree. All we have to do is add a load (and for demonstration purposes also a save) function, adjust it to our structures and we are good to go. Here is an example interface; if you want to compile the project afterwards I recommend saving it to include/datastructures.hpp:

#ifndef DATASTRUCTURES_HPP
#define DATASTRUCTURES_HPP

#include <boost/property_tree/ptree.hpp>
#include <boost/optional.hpp>
#include <string>
#include <vector>

namespace datastructures {
    using boost::property_tree::ptree;

    struct BaseStructure {
        std::string name;
        boost::optional<std::string> description;
        boost::optional<std::string> url_website;
        boost::optional<std::string> url_image;

        virtual void load(const ptree &pt) = 0;
        virtual ptree save() const = 0;
    };

    struct Player : BaseStructure {
        boost::optional<std::string> first_name;
        boost::optional<std::string> last_name;
        boost::optional<int> age;

        void load(const ptree &pt);
        ptree save() const;
    };

    struct Clan : BaseStructure {
        std::vector<Player> players;

        void load(const ptree &pt);
        ptree save() const;
    };

    struct Sponsor : BaseStructure {
        void load(const ptree &pt);
        ptree save() const;
    };

    struct Tournament : BaseStructure {
        std::vector<Sponsor> sponsors;
        std::vector<Clan> clans;

        void load(const ptree &pt);
        void load(const std::string &filename);
        ptree save() const;
        void save(const std::string &filename) const;
    };
}

#endif // DATASTRUCTURES_HPP

And here is the respective implementation source/datastructures.cpp:

#include "datastructures.hpp"
#include <boost/property_tree/xml_parser.hpp>
#include <boost/foreach.hpp>

using namespace datastructures;

void Player::load(const ptree &pt) {
    name = pt.get<std::string>("name");
    description = pt.get_optional<std::string>("description");
    url_website = pt.get_optional<std::string>("url_website");
    url_image = pt.get_optional<std::string>("url_image");

    first_name = pt.get_optional<std::string>("first_name");
    last_name = pt.get_optional<std::string>("last_name");
    age = pt.get_optional<int>("age");
}

ptree Player::save() const {
    ptree pt;

    pt.put("name", name);
    if(description) pt.put("description", *description);
    if(url_website) pt.put("url_website", *url_website);
    if(url_image) pt.put("url_image", *url_image);

    if(first_name) pt.put("first_name", *first_name);
    if(last_name) pt.put("last_name", *last_name);
    if(age) pt.put("age", *age);

    return pt;
}

void Clan::load(const ptree &pt) {
    name = pt.get<std::string>("name");
    description = pt.get_optional<std::string>("description");
    url_website = pt.get_optional<std::string>("url_website");
    url_image = pt.get_optional<std::string>("url_image");

    BOOST_FOREACH(const ptree::value_type &val, pt.get_child("players")) {
        Player player;
        player.load(val.second);
        players.push_back(player);
    }
}

ptree Clan::save() const {
    ptree pt;

    pt.put("name", name);
    if(description) pt.put("description", *description);
    if(url_website) pt.put("url_website", *url_website);
    if(url_image) pt.put("url_image", *url_image);

    for(std::vector<Player>::const_iterator player = players.begin();
        player != players.end(); player++)
    {
        pt.add_child("players.player", player->save());
    }

    return pt;
}

void Sponsor::load(const ptree &pt) {
    name = pt.get<std::string>("name");
    description = pt.get_optional<std::string>("description");
    url_website = pt.get_optional<std::string>("url_website");
    url_image = pt.get_optional<std::string>("url_image");
}

ptree Sponsor::save() const {
    ptree pt;

    pt.put("name", name);
    if(description) pt.put("description", *description);
    if(url_website) pt.put("url_website", *url_website);
    if(url_image) pt.put("url_image", *url_image);

    return pt;
}

void Tournament::load(const ptree &pt) {
    name = pt.get<std::string>("name");
    description = pt.get_optional<std::string>("description");
    url_website = pt.get_optional<std::string>("url_website");
    url_image = pt.get_optional<std::string>("url_image");

    BOOST_FOREACH(const ptree::value_type &val, pt.get_child("sponsors")) {
        Sponsor sponsor;
        sponsor.load(val.second);
        sponsors.push_back(sponsor);
    }

    BOOST_FOREACH(const ptree::value_type &val, pt.get_child("clans")) {
        Clan clan;
        clan.load(val.second);
        clans.push_back(clan);
    }
}

void Tournament::load(const std::string &filename) {
    ptree pt;
    read_xml(filename, pt);
    load(pt.get_child("tournament"));
}

ptree Tournament::save() const {
    ptree pt;

    pt.put("name", name);
    if(description) pt.put("description", *description);
    if(url_website) pt.put("url_website", *url_website);
    if(url_image) pt.put("url_image", *url_image);

    for(std::vector<Sponsor>::const_iterator sponsor = sponsors.begin();
        sponsor != sponsors.end(); sponsor++)
    {
        pt.add_child("sponsors.sponsor", sponsor->save());
    }

    for(std::vector<Clan>::const_iterator clan = clans.begin();
        clan != clans.end(); clan++)
    {
        pt.add_child("clans.clan", clan->save());
    }

    return pt;
}

void Tournament::save(const std::string &filename) const {
    ptree pt;
    pt.put_child("tournament", save());
    write_xml(filename, pt);
}

Isn't this incredibly easy? You could, by the way, blame me for breaking the DRY idiom, there is definitely space for improvement in that implementation, but it serves the little demonstration here. If you have not yet worked with property trees, I recommend doing a bit of playing around with these datastructures, and the XML files they produce before we continue to the next step, that will follow up with the next post.

3/20/2012

Welcome!

First post, yay. In this blog I am going to share projects, snippets and thoughts about C++ projects mainly focused on solving problems using the Boost C++ Libraries. Basically whenever I write something in a compiled programming language, C++ will be my weapon of choice. The primary reason for this actually are the Boost libraries. I am constantly switching between operating systems (I usually develop stuff on a Linux machine at home, and am using a Windows machine at work), and I require the code I write to be operable on both systems without further ado. The Boost libraries offer me this, and on top of it very generic and straight forward structures and patterns for pretty much everything I want to do. David Johnson from Integrated Computer Solutions put it this way in his talk during the Qt Developer Days 2009:
"If you know the STL, think of Boost as being the STL+."
The things I will be covering here are compiled and unit tested by me, but just by me. There might still be bugs or performance issues with some of them, and I would appreciate if you pointed these out in a comment should you find one. I have compiled and tested the code using Boost 1.49.0. If you are using a different version, you usually are fine as long as the libraries used are part of that version (you can check that here), but if there are problems, this might be the reason for it.

For the first post, I will demonstrate the power of Boost('s smart pointers) with a very short snippet. The context: Nowadays, web services are everywhere and SOAP is widely spread. I am using gSOAP for developing clients for web services. SOAP services are usually defined in form of WSDL files. Parts of structures or whole structures sent or received may be defined as optional, and gSOAP will translate optional as a pointer to that structure. A null pointer evaluates to the optional value has not been set. While this seems convenient at first thought, it leads to the troublesome recognition that you will often have to deal with massive pointer structures. You can, of course, allocate memory for your structures with the new operator, but you might to forget to free the allocated memory afterwards, which would cause your application to leak. We can go around this issue using the Boost smart pointers:

#include <boost shared_ptr.hpp>
#include <vector>

struct DemoStruct {
    int *some_int;
    double *some_double;
};

typedef std::vector<boost::shared_ptr<void> > MemoryManager;

template <class T> T* create_managed(T *value, MemoryManager &mm) {
    mm.push_back(boost::shared_ptr<T>(value));
    return value;
}

int main(int, char**) {
    MemoryManager mm;
    DemoStruct *demo_struct = create_managed(new DemoStruct, mm);
    demo_struct->some_int = create_managed(new int(42), mm);
    demo_struct->some_double = create_managed(new double(4.2), mm);
    return 0;
} 

As you can see, we have written a generic function that deals with the memory management using the Boost smart pointer library with just two lines of code.

Next time, we will look at a more elaborated project - I have in mind a file listener service that parses XML files in a certain directory to do something with the information inside. Thanks for reading and stay tuned for more fun with Boost.