[odb-users] Handling pimpl idiom in ODB

Boris Kolpackov boris at codesynthesis.com
Wed Aug 17 10:03:26 EDT 2011


[Original message sent privately. CC'ing odb-users into the discussion.]

Hi Uwe,

cetoni GmbH - Uwe Kindler <uwe.kindler at cetoni.de> writes:

> Our Qt based software is plugin based and uses a lot of shared  
> libraries. In order to ensure binary compatibility and to lower the  
> needs to recompile libraries we prefer to use the pimpl idiom like it is  
> used in the Qt framework. Until now we did not use the idiom for all  
> classes that are stored into database by ODB because ODB needs access to  
> the members. Because these persistent classes are used by a lot of our  
> plugins, adding or removing only a single member variable causes a  
> recompile of all shared libraries and plugins because binary  
> compatibility is broken.
>
> That was the reason for me, to think about ODB and the use of the pimpl  
> idiom at the same time and I created a small test case that works:
>
> #pragma db object
> class COdbPimplTestPrivate
> {
> public:
>     #pragma db id auto
>     long Id;
>     double  DoubleValue;
>     int     IntValue;
>     QString Name;
> };
>
>
> #pragma db object
> class COdbPimplTest
> {
>     friend class odb::access;
> public:
>     #pragma db id auto
>     long m_Id;
>     boost::shared_ptr<COdbPimpTestPrivate> d;
>
> public:
>     COdbPimplTest();
>     double doubleValue() const;
> };
>
> So the real data members are stored in the private class and the public  
> class has only a pointer to the private class. This results in two  
> tables - one for the private class with all the data and one for the  
> public class that consists only of the ID and the foreign key to the  
> private class. What do you hink about this design? Do you have a better  
> idea to use ODB and the pimpl idiom at the same time or is there any  
> best practice for ODB and pimpl classes.

The method that you have described works but is not ideal. It is wasteful
in the sense that it results in two tables per object as well as requires
two SELECT statements to load an object. It is also conceptually incorrect
since there is really only one object, not two.

After thinking some more about this, I see two ways how a pimpl idiom
can be handled. 

With the first approach we make the 'impl' class an ODB object and store
that in the database. Because the 'impl' class is the implementation
detail of the 'interface' class, with this approach we would also have
to hide (or wrap) the database loading/storing logic. In other words,
the persistence of the class becomes its implementation details. Here
is how this can look:

#pragma db object
class COdbPimplTestPrivate
{
public:
    #pragma db id auto
    long Id;
    double  DoubleValue;
    int     IntValue;
    QString Name;
};

class COdbPimplTest
{  
    boost::shared_ptr<COdbPimpTestPrivated> d;

public:   
    // Database operations.
    //
    COdbPimplTest(odb::database& db, long id)
    {
      d = db.load<COdbPimplTestPrivate> (id);
    }

    void persist (odb::database& db)
    {
      db->persist (d);
    }

    void update (odb::database& db)
    {
      db->update (d);
    }
};

The major limitation of this approach is the fact that you have to
wrap the odb::database operations that are normally available to the
persistent objects. This can be easy for operations like persist() and
load() and more complicated for query() (because COdbPimplTest is not
a persistent object, we cannot use odb::query<COdbPimplTest> or
odb::result<COdbPimplTest>). Handling inheritance with this approach
can also be tricky. Though inheritance is generally tricky with pimpl.
This approach is supported by ODB now.

The alternative approach is to make the 'interface' class the ODB
persistent object and treat the 'impl' class as just details of how
the data is stored. While both approaches have their merits and which
one to use will ultimately depend on the application requirements,
IMO, the second approach better reflects the purpose of why we use
the pimpl idiom in the first place.

Now, there is no way to support the second approach in the current
state of ODB (as of version 1.5.0) because, as you have pointed out,
ODB needs direct access to the object's data members. However, for
some time now, we have been thinking about a feature that would relax
this requirement and which would allow us to handle the second pimpl
approach as well as some other use-cases, such as objects with
private data members that cannot be modified by adding the friend
declaration.

In a nutshell, the idea is to have what we call "virtual data members"
which are not really data members but rather a pair of accessor and
modifier expressions. Here is an example (the exact syntax is not yet
finalized):

class COdbPimplTestPrivate
{
public:    
    long Id;
    double  DoubleValue;
    int     IntValue;
    QString Name;
};

#pragma db object
class COdbPimplTest
{  
    #pragma db transient
    boost::shared_ptr<COdbPimpTestPrivated> d;
      
public:
    double doubleValue() const;
    void doubleValue(double) const;

    #pragma db member(Id) virtual(long) get(this->d->Id) id auto

    #pragma db member(DoubleValue) virtual(double) \
      get(this->doubleValue ()) set(this->doubleValue (?))

  ...
};

Which approach do you think will work best for your case? If it is the
second, do you see any issues with the virtual member-based implementation?

Boris



More information about the odb-users mailing list