Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

C/C++

Supporting Custom C++ Types


Of course, in practice, users will often prefer to work with other types, such as nonstandard String, Money, or Date classes. For example, if your development team has adopted the Boost libraries, you might like to use boost::gregorian::date ( which can be found at boost.org) directly with SOCI, as in Listing Three.

Listing Three
 

// Requires support for boost::gregorian::date to be added, either
// manually as in Listing Four, or via TypeConversion<T> as in Listing Six.
#include "listing6.h"
#include <soci.h>
#include <boost/date_time/gregorian/gregorian.hpp>
#include <iostream>

using namespace SOCI;
using boost::gregorian::date;
using boost::gregorian::months_of_year;

int main()
{
    try
    {
        Session sql("oracle", "service=gen1 user=scott " "password=tiger");
        sql << "create table person(id number, name varchar2(50),"
                 " birthday date)";
        int id(100);
        std::string name("Bjarne");
        date bday(2001, boost::gregorian::Jan, 1);
        sql << "insert into person values(:id, :name, :birthday)",
               use(id), use(name), use(bday);
        std::tm bd;
        sql<<"select birthday from person", into(bd);
        date bday2(boost::gregorian::not_a_date_time);
        sql << "select birthday from person", into(bday2);
        assert(bday2 == bday);
        std::cout<<name<<"'s birthday is "<<bday2<<std::endl;        

        sql << "drop table person";
    }
    catch(std::exception& e)
    {
        std::cout<<e.what()<<std::endl;
    }
}
#ifndef LISTING6_H
#define LISTING6_H

SOCI could of course be made to support boost::gregorian::date "out of the box," but that wouldn't solve the larger problem. One of the realities of C++ is that, for better or worse, there are tens—if not hundreds—of String, Numeric, and Date classes in widespread use.

Since the SOCI implementation is driven by template specialization, there is a natural way you could add support for your preferred custom type. For example, to add support for boost::gregorian::date, you could provide explicit specializations for UseType<date> and IntoType<date>, as in Listing Four.

Listing Four
#include <boost/date_time/gregorian/gregorian.hpp>
using boost::gregorian::months_of_year;
using boost::gregorian::date;

template <> class IntoType<date> : public IntoType<std::tm>
{
public:
    IntoType(std::time_t &t) : IntoType<std::tm>(tm_), t_(t) {}

    virtual void postFetch(bool gotData, bool calledFromFetch)
    {
        IntoType<std::tm>::postFetch(gotData, calledFromFetch);
        if (gotData)
        {
            std::tm* t = static_cast<std::tm*>(data_);
	        value_ = date(t->tm_year + 1900, 
			  static_cast<months_of_year>(t.tm_mon + 1),
			  t->tm_mday);	
        }
    }
private:
    date& value_;
};

template <>
class UseType<std::time_t> : public UseType<std::tm>
{
public:
    UseType(std::time_t &t, std::string const &name = std::string())
        : UseType<std::tm>(tm_, name), t_(t) {}

    virtual void preUse()
    {
        std::tm* t = static_cast<std::tm*>(data_);
        t->tm_isdst = -1;
        t->tm_year = value_.year() - 1900;
        t->tm_mon = value_.month() - 1;
        t->tm_mday = value_.day();
        t->tm_hour = 0;
        t->tm_min = 0;
        t->tm_sec = 0;
        std::mktime(t); 

	UseType<std::tm>::preUse();
    }

    virtual void postUse()
    {
	UseType<std::tm>::postUse();

	std::tm* t = static_cast<std::tm*>(data_);
	value_ = date(t->tm_year + 1900, 
		      static_cast<months_of_year>(t->tm_mon + 1),
		      t->tm_mday);
    }

private:
    date& value_;
};

Figure 2 presents the corresponding UML class diagram.


Figure 2: Manually adding support for custom types.

In these example specializations, I've chosen to leverage the existing database access support for std::tm by having UseType<date> inherit from UseType<std::tm> and IntoType<date> inherit from IntoType<std::tm>.

A member variable value_ of type date holds a reference to the parameter, which is passed to into() and use(). In the case of IntoType<date>, the value_ data member is populated inside postFetch(), which is called by the framework after the database-related code is done executing.

Similarly, for UseType<date>, the value_ member is copied into StandardIntoType::data_ in preUse() before any database-related code executes. Because the parameter passed to the use() function can also be written to when used in conjunction with stored procedures, the value_ data member is also populated inside postUse();.

Traits, TypeConversion<T>, & Generalized Support for New Types

Adding support for new types in the manner just described is effective, but it requires you to have significant knowledge of the relevant library internals. After studying code that used the method in Listing Four, I realized that the implementation technique could be generalized. I therefore made several enhancements to the SOCI library to make defining new types simpler for library users. The key to the enhancements is a traits class called TypeConversion.

Defined in terms of its structure, a traits class is simply a class template, which contains only typedefs and static functions, and has no modifiable state or virtuals (see C++ Coding Standards by Herb Sutter and Andrei Alexandrescu, Addison-Wesley, 2004).

Nathan Meyers, who first introduced the traits concept, defines a traits class as: "A class used in place of template parameters. As a class, it aggregates useful types and constants; as a template, it provides an avenue for that 'extra level of indirection' that solves all software problems" (www.cantrip.org/traits.html).

Andrei Alexandrescu has written that traits are intended to "consolidate pieces of code that, depending upon a type...sport slight variations in terms of structure and/or behavior. To achieve this end, traits rely on explicit template specialization" (erdani.org/publications/traits.html).

There are three required class members for a valid TypeConversion trait class specialization:

  • The base_type typedef, defining the base type.
  • The from() static member function, converting from the base type.
  • The to() static member function, converting to the base type.

Listing Five shows the relevant changes to the SOCI framework.

Listing Five
class StandardIntoType : public IntoTypeBase
{
public:
    StandardIntoType(void *data, eExchangeType type);
    virtual ~StandardIntoType();

private:
    virtual void define(Statement &st, int &position);
    virtual void preFetch();
    virtual void postFetch(bool gotData, bool calledFromFetch);
    virtual void cleanUp();

    // conversion hook (from base type to arbitrary user type)
    virtual void convertFrom() {}

    void *data_;
    eExchangeType type_;
    StandardIntoTypeBackEnd *backEnd_;
};

class StandardUseType : public UseTypeBase
{
public:
    StandardUseType(void *data, eExchangeType type,
        std::string const &name = std::string());

    virtual ~StandardUseType();
    virtual void bind(Statement &st, int &position);

private:
    virtual void preUse();
    virtual void postUse(bool gotData);
    virtual void cleanUp();

    // conversion hooks (from arbitrary user type to base type)
    virtual void convertTo() {}
    virtual void convertFrom() {}

    void *data_;
    eExchangeType type_;
    std::string name_;

    details::StandardUseTypeBackEnd *backEnd_;
};

struct BaseValueHolder
{
    typename TypeConversion<T>::base_type val_;
};

// Automatically create an IntoType from a TypeConversion
template <typename T> class IntoType
    : private BaseValueHolder<T>,
      public IntoType<typename TypeConversion<T>::base_type>
{
public:
    typedef typename TypeConversion<T>::base_type BASE_TYPE;

    IntoType(T &value)
        : IntoType<BASE_TYPE>(BaseValueHolder<T>::val_),
          value_(value) {}

private:
    void convertFrom()
    {
        value_ = TypeConversion<T>::from(BaseValueHolder<T>::val_);
    }

    T &value_;
};

// Automatically create a UseType from a TypeConversion
template <typename T>
class UseType
    : private details::BaseValueHolder<T>,
      public UseType<typename TypeConversion<T>::base_type>
{
public:
    typedef typename TypeConversion<T>::base_type BASE_TYPE;

    UseType(T &value)
        : UseType<BASE_TYPE>(BaseValueHolder<T>::val_),
          value_(value) {}

private:
    void convertFrom()
    {
        value_ = TypeConversion<T>::from(BaseValueHolder<T>::val_);
    }
    void convertTo()
    {
        details::BaseValueHolder<T>::val_ = 
		TypeConversion<T>::to(value_);
    }

    T &value_;
};

The first step to add support for TypeConversion within the framework was to use the Template Method pattern (www.ddj.com/dept/cpp/184401436) by adding private virtual functions convertFrom() and convertTo() to classes StandardIntoType and StandardUseType. These new member functions are called from StandardUseType::preUse(),StandardUseType::post Use(), and StandardIntoType::postFetch().

I used the Template Method pattern here to isolate the code, which is unique to UseType and IntoType subclasses inside the convertFrom() and convertTo() private member functions. This simplifies the implementation of subclasses because they no longer need to include calls to their corresponding base class methods.

The next step was to partially specialize classes IntoType and UseType so that they inherit from IntoType<TypeConversion<T>::base_type> and UseType<TypeConversion<T>:: base_type>, respectively. These new classes also override convertTo() and convertFrom() and call TypeConversion <T>::to() and TypeConversion<T>::from().

An additional implementation detail is that these new IntoType and UseType specializations also inherit from the BaseValueHolder struct, which is used to hold the val_ member of type TypeConversion<T>::base_type. This ensures the correct order of initialization for the relevant member variables.

With the framework enhanced in this way, if you want to add SOCI support for boost::gregorian::date, you need only provide the appropriate TypeConversion specialization. IntoType<date> and UseType<date> are automatically generated by the compiler.

Compare this new method for adding custom types (Listing Six) to the old method (Listing Four). The obvious advantages are simplicity and separation of responsibilities. With the new method, you are not burdened with knowing the details of the SOCI implementation. The code that you need to provide to add support for a new type is isolated to the TypeConversion class template.

Listing Six

#include <iostream>
#include <soci.h>
#include <boost/date_time/gregorian/gregorian.hpp>

using boost::gregorian::months_of_year;
using boost::gregorian::date;

namespace SOCI
{
template<> struct TypeConversion<date>
{
    typedef std::tm base_type;
    static date from(std::tm& t)
    {
        date d( t.tm_year + 1900, 
             static_cast<months_of_year>(t.tm_mon + 1), t.tm_mday );
        return d;
    }
    static std::tm to(date& d)
    {
        std::tm t;
        t.tm_isdst = -1;
        t.tm_year = d.year() - 1900;
        t.tm_mon = d.month() - 1;
        t.tm_mday = d.day();
        t.tm_hour = 0;
        t.tm_min = 0;
        t.tm_sec = 0;
        std::mktime(&t); 
        return t;
    }
};
};
#endif


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.