From 30d953064e9cbe98c0810433cf6fff61b7805c0c Mon Sep 17 00:00:00 2001 From: Boris Kolpackov Date: Tue, 11 Jan 2011 16:09:04 +0200 Subject: Add chapter on object relationships --- doc/manual.xhtml | 995 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 995 insertions(+) (limited to 'doc') diff --git a/doc/manual.xhtml b/doc/manual.xhtml index 1bd001b..396c0ea 100644 --- a/doc/manual.xhtml +++ b/doc/manual.xhtml @@ -2628,6 +2628,1001 @@ private: --options-file hashtable.options + + + + +

Y Relationships

+ +

Relationships between persistent objects are expressed with pointers or + containers of pointers. The ODB runtime library provides built-in + support for the TR1 shared_ptr/weak_ptr, + std::auto_ptr, and raw pointers. Plus, ODB profile + libraries are available for commonly used frameworks and libraries + (such as Boost and Qt) that provide support for smart pointers found + in these frameworks and libraries. It is also easy to add support + for a custom smart pointer as discussed later in + Section Y.4, "Using Custom Smart Pointers". Any supported + smart pointer can be used in a data member as long as it can be + explicitly constructed from the canonical object pointer (@@ ref). + For example, you can use weak_ptr if the object pointer + is shared_ptr.

+ +

When an object containing a pointer to another object is loaded, + the pointed-to object is loaded as well. In some situations this + eager loading of the relationships is undesirable since it + can lead to a large number of otherwise unused objects being + instantiated from the database. To support finer control + over relationships loading, the ODB runtime and profile + libraries provide the so-called lazy versions of + the supported pointers. An object pointed-to by a lazy pointer + is not loaded automatically when the containing object is loaded. + Instead, we have to explicitly request the instantiation of the + pointed-to object. Lazy pointers are discussed in + detail in Section Y.3, "Lazy Pointers".

+ +

As a simple example, consider the following employee-employer + relationship. Code examples presented in this chapter + will use the shared_ptr and weak_ptr + smart pointers from the TR1 (std::tr1) namespace.

+ +
+#pragma db object
+class employer
+{
+  ...
+
+  #pragma db id
+  std::string name_;
+};
+
+#pragma db object
+class employee
+{
+  ...
+
+  #pragma db id
+  unsigned long id_;
+
+  std::string first_name_;
+  std::string last_name_;
+
+  shared_ptr<employer> employer_;
+};
+  
+ +

By default, an object pointer can be NULL. To + specify that a pointer always point to a valid object we can + use the not_null pragma, for example:

+ +
+#pragma db object
+class employee
+{
+  ...
+
+  #pragma db not_null
+  shared_ptr<employer> employer_;
+};
+  
+ +

In this case, if we perform a database operation on the + employee object and the employer_ + pointer is NULL, then the odb::null_pointer + exception will be thrown.

+ +

We don't need to do anything special to establish or navigate a + relationship between two persistent objects, as shown in the + following code fragment:

+ +
+// Create an employer and a few employees.
+//
+unsigned long john_id, jane_id;
+{
+  shared_ptr<employer> er (new employer ("Example Inc"));
+  shared_ptr<employee> john (new employee ("John", "Doe"));
+  shared_ptr<employee> jane (new employee ("Jane", "Doe"));
+
+  john->employer_ = er;
+  jane->employer_ = er;
+
+  transaction t (db.begin ());
+
+  db.persist (er);
+  john_id = db.persist (john);
+  jane_id = db.persist (jane);
+
+  t.commit ();
+}
+
+// Load a few employee objects and print their employer.
+//
+{
+  session s;
+  transaction t (db.begin ());
+
+  shared_ptr<employee> john (db.load<employee> (john_id));
+  shared_ptr<employee> jane (db.load<employee> (jane_id));
+
+  cout << john->employer_->name_ << endl;
+  cout << jane->employer_->name_ << endl;
+
+  t.commit ();
+}
+  
+ +

The only notable line in the above code is the creation of a + session before the second transaction starts. As discussed in + Chapter @@ ref, a session acts as a cache of persistent objects. + By creating a session before loading the employee + objects we make sure that their employer_ pointers + point to the same employer object. Without a + session, each employee would have ended up pointing + to its own, private instance of the Example Inc employer.

+ +

As a general guideline, you should use a session when loading + objects that have pointers to other persistent objects. A + session makes sure that for a given object id, a single instance + is shared among all other objects that relate to it.

+ +

We can also use the data members from the pointed-to + objects in database queries (Chapter 4, "Querying the + Database"). For each pointer in a persistent class, the query + class defines a nested scope containing members corresponding + to the data members in the pointed-to object. For example, the + query class for the employee object contains + the employer scope (derived from the employer_ + pointer) which in turn contains the name member + (derived from the employer::name_ data member of the + pointed-to object). As a result, we can use the + query::employer::name expression while querying + the database for the employee objects. For example, + the following transaction finds all the employees of Example Inc + that have the Doe last name:

+ +
+typedef odb::query<employee> query;
+typedef odb::result<employee> result;
+
+session s;
+transaction t (db.begin ());
+
+result r (db->query<employee> (
+  query::employer::name == "Example Inc" && query::last == "Doe"));
+
+for (result::iterator i (r.begin ()); i != r.end (); ++i)
+  cout << i->first_ << " " << i->last_ << endl;
+
+t.commit ();
+  
+ + +

An important concept to keep in mind when working with object + relationships is the independence of persistent objects. In particular, + when an object containing a pointer to another object is made persistent + or is updated, the pointed-to object is not automatically persisted + or updated. Rather, only a reference to the object (in the form of the + object id) is stored for the pointed-to object in the database. + The pointed-to object itself is a separate entity and should + be made persistent or updated independently.

+ +

When persisting or updating an object containing a pointer to another + object, the pointed-to object must have a valid object id. This, + however, may not always be easy to achieve in complex relationships that + involve objects with automatically assigned identifiers. In such + cases it may be necessary to first persist an object with a pointer + set to NULL and then, once the pointed-to object is + made persistent and its identifier assigned, set the pointer + to the correct value and update the object in the database.

+ +

Persistent object relationships can be divided into two groups: + unidirectional and bidirectional. Each group in turn contains + several configurations that vary depending on the cardinality + of the sides of the relationship. All possible unidirectional + and bidirectional configurations are discussed in the following + sections.

+ +

Y.1 Unidirectional Relationships

+ +

In unidirectional relationships we are only interested in navigating + from object to object in one direction. Because there is no interest + in navigating in the opposite direction, the cardinality of the other + end of the relationship is unimportant. As a result, there are only + two possible unidirectional relationships: to-one and to-many. Each + of these relationships is described in the following sections. For + sample code that shows how to work with these relationships, refer + to the relationship example in the odb-examples + package.

+ +

Y.1.1 To-One Relationships

+ +

An example of a unidirectional to-one relationship is the + employee-employer relationship (an employee has one employer). + The following persistent C++ classes model this relationship:

+ +
+#pragma db object
+class employer
+{
+  ...
+
+  #pragma db id
+  std::string name_;
+};
+
+#pragma db object
+class employee
+{
+  ...
+
+  #pragma db id
+  unsigned long id_;
+
+  #pragma db not_null
+  shared_ptr<employer> employer_;
+};
+  
+ +

The corresponding database tables look like this:

+ +
+CREATE TABLE employer (
+  name VARCHAR (255) NOT NULL PRIMARY KEY);
+
+CREATE TABLE employee (
+  id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
+  employer VARCHAR (255) NOT NULL REFERENCES employer (name));
+  
+ +

Y.1.2 To-Many Relationships

+ +

An example of a unidirectional to-many relationship is the + employee-project relationship (an employee can be involved + in multiple projects). The following persistent C++ classes + model this relationship:

+ +
+#pragma db object
+class project
+{
+  ...
+
+  #pragma db id
+  std::string name_;
+};
+
+#pragma db object
+class employee
+{
+  ...
+
+  #pragma db id
+  unsigned long id_;
+
+  #pragma db not_null unordered
+  std::vector<shared_ptr<project> > projects_;
+};
+  
+ +

The corresponding database tables look like this:

+ +
+CREATE TABLE project (
+  name VARCHAR (255) NOT NULL PRIMARY KEY);
+
+CREATE TABLE employee (
+  id BIGINT UNSIGNED NOT NULL PRIMARY KEY);
+
+CREATE TABLE employee_projects (
+  object_id BIGINT UNSIGNED NOT NULL,
+  value VARCHAR (255) NOT NULL REFERENCES project (name));
+  
+ +

To obtain a more canonical database schema, the names of tables + and columns above can be customized using ODB pragmas + (Chapter 5, "ODB Pragma Language"). For example:

+ +
+#pragma db object
+class employee
+{
+  ...
+
+  #pragma db not_null unordered \
+             id_column("employee_id") value_column("project_name")
+  std::vector<shared_ptr<project> > projects_;
+};
+  
+ +

The resulting employee_projects table would then + look like this:

+ +
+CREATE TABLE employee_projects (
+  employee_id BIGINT UNSIGNED NOT NULL,
+  project_name VARCHAR (255) NOT NULL REFERENCES project (name));
+  
+ + +

Y.2 Bidirectional Relationships

+ +

In bidirectional relationships we are interested in navigating + from object to object in both directions. As a result, each + object class in a relationship contains a pointer to the other + object. If smart pointers are used, then a weak pointer should + be used as one of the pointers to avoid ownership cycles. For + example:

+ +
+class employee;
+
+#pragma db object
+class position
+{
+  ...
+
+  #pragma db id
+  unsigned long id_;
+
+  weak_ptr<employee> employee_;
+};
+
+#pragma db object
+class employee
+{
+  ...
+
+  #pragma db id
+  unsigned long id_;
+
+  #pragma db not_null
+  shared_ptr<position> position_;
+};
+  
+ +

Note that when we establish a bidirectional relationship, we + have to set both pointers consistently. One way to make sure + that a relationship is always in a consistent state is to + provide a single function that updates both pointers at the + same time. For example:

+ +
+#pragma db object
+class position: public enable_shared_from_this<position>
+{
+  ...
+
+  void
+  fill (shared_ptr<employee> e)
+  {
+    employee_ = e;
+    e->positions_ = shared_from_this ();
+  }
+
+private:
+  weak_ptr<employee> employee_;
+};
+
+#pragma db object
+class employee
+{
+  ...
+
+private:
+  friend class position;
+
+  #pragma db not_null
+  shared_ptr<position> position_;
+};
+  
+ + +

Above, to model a bidirectional relationship in persistent classes, + we used two pointers, one in each object. While this is a natural + representation in C++, it does not translate to a canonical + relational model. Consider the database schema generated for + the above two classes:

+ +
+CREATE TABLE position (
+  id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
+  employee BIGINT UNSIGNED REFERENCES employee (id));
+
+CREATE TABLE employee (
+  id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
+  position BIGINT UNSIGNED NOT NULL REFERENCES position (id));
+  
+ +

While this database schema is valid, it is unconventional. We have + a reference from a row in the position table to a row + in the employee table. We also have a reference + from this same row in the employee table back to + the row in the position table. From the relational + point of view, one of these references is redundant since + in SQL we can easily navigate in both directions using just one + of these references.

+ +

To eliminate redundant database schema references we can use the + inverse pragma (Section 5.4.7, + "inverse") which tells the ODB compiler that + a pointer is the inverse side of a bidirectional relationship. + Either side of a relationship can be made inverse. For example:

+ +
+#pragma db object
+class position
+{
+  ...
+
+  #pragma db inverse(position_)
+  weak_ptr<employee> employee_;
+};
+
+#pragma db object
+class employee
+{
+  ...
+
+  #pragma db not_null
+  shared_ptr<position> position_;
+};
+  
+ +

The resulting database schema looks like this:

+ +
+CREATE TABLE position (
+  id BIGINT UNSIGNED NOT NULL PRIMARY KEY);
+
+CREATE TABLE employee (
+  id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
+  position BIGINT UNSIGNED NOT NULL REFERENCES position (id));
+  
+ +

As you can see, an inverse member does not have a corresponding + column (or table, in case of an inverse container of pointers) + and, from the point of view of database operations, is effectively + read-only. The only way to change a bidirectional relationship + with an inverse side is to set its direct (non-inverse) + pointer. Also note that an ordered container (Section + X.1, "Ordered Containers") of pointers that is an inverse side + of a bidirectional relationship is always treated as unordered + (Section 5.4.8, "unordered") + because the contents of such a container are implicitly built from + the direct side of the relationship which does not contain the + element order (index).

+ +

There are three distinct bidirectional relationships that we + will cover in the following sections: one-to-one, one-to-many, + and many-to-many. We will only talk about bidirectional + relationships with inverse sides since they result in canonical + database schemas. For sample code that shows how to work with + these relationships, refer to the inverse example + in the odb-examples package.

+ +

Y.2.1 One-to-One Relationships

+ +

An example of a bidirectional one-to-one relationship is the + presented above employee-position relationship (an employee + fills one position and a position is filled by one employee). + The following persistent C++ classes model this relationship:

+ +
+class employee;
+
+#pragma db object
+class position
+{
+  ...
+
+  #pragma db id
+  unsigned long id_;
+
+  #pragma db inverse(position_)
+  weak_ptr<employee> employee_;
+};
+
+#pragma db object
+class employee
+{
+  ...
+
+  #pragma db id
+  unsigned long id_;
+
+  #pragma db not_null
+  shared_ptr<position> position_;
+};
+  
+ +

The corresponding database tables look like this:

+ +
+CREATE TABLE position (
+  id BIGINT UNSIGNED NOT NULL PRIMARY KEY);
+
+CREATE TABLE employee (
+  id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
+  position BIGINT UNSIGNED NOT NULL REFERENCES position (id));
+  
+ +

If instead the other side of this relationship is made inverse, + then the database tables will change as follows:

+ +
+CREATE TABLE position (
+  id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
+  employee BIGINT UNSIGNED REFERENCES employee (id));
+
+CREATE TABLE employee (
+  id BIGINT UNSIGNED NOT NULL PRIMARY KEY);
+  
+ +

Y.2.2 One-to-Many Relationships

+ +

An example of a bidirectional one-to-many relationship is the + employer-employee relationship (an employer has multiple + employees and an employee is employed by one employer). + The following persistent C++ classes model this relationship:

+ +
+class employee;
+
+#pragma db object
+class employer
+{
+  ...
+
+  #pragma db id
+  std::string name_;
+
+  #pragma db not_null inverse(employer_)
+  std::vector<weak_ptr<employee> > employees_
+};
+
+#pragma db object
+class employee
+{
+  ...
+
+  #pragma db id
+  unsigned long id_;
+
+  #pragma db not_null
+  shared_ptr<employer> employer_;
+};
+  
+ +

The corresponding database tables differ significantly depending + on which side of the relationship is made inverse. If the one + side (employer) is inverse as in the code + above, then the resulting database schema looks like this:

+ +
+CREATE TABLE employer (
+  name VARCHAR (255) NOT NULL PRIMARY KEY);
+
+CREATE TABLE employee (
+  id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
+  employer VARCHAR (255) NOT NULL REFERENCES employer (name));
+  
+ +

If instead the many side (employee) of this + relationship is made inverse, then the database tables will change + as follows:

+ +
+CREATE TABLE employer (
+  name VARCHAR (255) NOT NULL PRIMARY KEY);
+
+CREATE TABLE employer_employees (
+  object_id VARCHAR (255) NOT NULL,
+  value BIGINT UNSIGNED NOT NULL REFERENCES employee (id));
+
+CREATE TABLE employee (
+  id BIGINT UNSIGNED NOT NULL PRIMARY KEY);
+  
+ +

Y.2.3 Many-to-Many Relationships

+ +

An example of a bidirectional many-to-many relationship is the + employee-project relationship (an employee can work on multiple + projects and a project can have multiple participating employees). + The following persistent C++ classes model this relationship:

+ +
+class employee;
+
+#pragma db object
+class project
+{
+  ...
+
+  #pragma db id
+  std::string name_;
+
+  #pragma db not_null inverse(projects_)
+  std::vector<weak_ptr<employee> > employees_;
+};
+
+#pragma db object
+class employee
+{
+  ...
+
+  #pragma db id
+  unsigned long id_;
+
+  #pragma db not_null unordered
+  std::vector<shared_ptr<project> > projects_;
+};
+  
+ +

The corresponding database tables look like this:

+ +
+CREATE TABLE project (
+  name VARCHAR (255) NOT NULL PRIMARY KEY);
+
+CREATE TABLE employee (
+  id BIGINT UNSIGNED NOT NULL PRIMARY KEY);
+
+CREATE TABLE employee_projects (
+  object_id BIGINT UNSIGNED NOT NULL,
+  value VARCHAR (255) NOT NULL REFERENCES project (name));
+  
+ +

If instead the other side of this relationship is made inverse, + then the database tables will change as follows:

+ +
+CREATE TABLE project (
+  name VARCHAR (255) NOT NULL PRIMARY KEY);
+
+CREATE TABLE project_employees (
+  object_id VARCHAR (255) NOT NULL,
+  value BIGINT UNSIGNED NOT NULL REFERENCES employee (id));
+
+CREATE TABLE employee (
+  id BIGINT UNSIGNED NOT NULL PRIMARY KEY);
+  
+ +

Y.3 Lazy Pointers

+ +

Consider again the bidirectional, one-to-many employer-employee + relationship that was presented earlier in this chapter:

+ +
+class employee;
+
+#pragma db object
+class employer
+{
+  ...
+
+  #pragma db id
+  std::string name_;
+
+  #pragma db not_null inverse(employer_)
+  std::vector<weak_ptr<employee> > employees_;
+};
+
+#pragma db object
+class employee
+{
+  ...
+
+  #pragma db id
+  unsigned long id_;
+
+  #pragma db not_null
+  shared_ptr<employer> employer_;
+};
+  
+ +

Consider also the following transaction which obtains the employer + name given the employee id:

+ +
+unsigned long id = ...
+string name;
+
+session s;
+transaction t (db.begin ());
+
+shared_ptr<employee> e (db.load<employee> (id));
+name = e->employer_->name_;
+
+t.commit ();
+  
+ +

While this transaction looks very simple, it actually does a lot more + than what meets the eye and is necessary. Consider what happens when + we load the employee object: the employer_ + pointer is also automatically loaded which means the employer + object corresponding to this employee is also loaded. But the + employer object in turn contains the list of pointers + to all the employees, which are also loaded. A a result, when object + relationships are involved, a simple transaction like the above can + load many more objects than is necessary.

+ +

To overcome this problem ODB offers finer grained control over + the relationship loading in the form of lazy pointers. A lazy + pointer does not automatically load the pointed-to object + when the containing object is loaded. Instead, we have to + explicitly load the pointed-to object if and when we need to + access it.

+ +

The ODB runtime library provides lazy counterparts for all the + supported pointers, namely: odb::lazy_shared_ptr and + odb::lazy_weak_ptr for TR1 shared_ptr and + weak_ptr, odb::lazy_auto_ptr for + std::auto_ptr, and odb::lazy_ptr for raw + pointers. The ODB profile libraries provide lazy pointer + implementations for smart pointers from popular frameworks and + libraries.

+ +

While we will discuss the interface of lazy pointers in more detail + shortly, the most commonly used extra function provided by these + pointers is load(). This function loads the + pointed-to object if it hasn't already been loaded. After + the call to this function, the lazy pointer can be used + in the the same way as its eager counterpart. The load() + function also returns the eager pointer, in case you need to pass + it around. For a lazy weak pointer, the + load() function also locks the pointer.

+ +

The following example shows how we can change our employer-employee + relationship to use lazy pointers. Here we choose to use lazy pointers + for both sides of the relationship.

+ +
+class employee;
+
+#pragma db object
+class employer
+{
+  ...
+
+  #pragma db not_null inverse(employer_)
+  std::vector<lazy_weak_ptr<employee> > employees_;
+};
+
+#pragma db object
+class employee
+{
+  ...
+
+  #pragma db not_null
+  lazy_shared_ptr<employer> employer_;
+};
+  
+ +

And the transaction is changed like this:

+ +
+unsigned long id = ...
+string name;
+
+session s;
+transaction t (db.begin ());
+
+shared_ptr<employee> e (db.load<employee> (id));
+e->employer_.load ();
+name = e->employer_->name_;
+
+t.commit ();
+  
+ + +

As a general guideline we recommend that you make at least one side + of a bidirectional relationship lazy, especially for relationships + with a many side.

+ +

A lazy pointer implementation mimics the interface of its eager + counterpart which can be used once the pointer is loaded. It also + adds a number of additional functions that are specific to the + lazy loading functionality. Overall, the interface of a lazy + pointer follows this general outline:

+ +
+template <class T>
+class lazy_ptr
+{
+public:
+  //
+  // The eager pointer interface.
+  //
+
+  // Initialization/assignment from an eager pointer.
+  //
+public:
+  template <class Y> lazy_ptr (const eager_ptr<Y>&);
+  template <class Y> lazy_ptr& operator= (const eager_ptr<Y>&);
+
+  // Lazy loading interface.
+  //
+public:
+  bool loaded () const;
+  eager_ptr<T> load () const;
+
+  // Unload the pointer. For transient objects this function is
+  // equivalent to reset().
+  //
+  void unload () const;
+
+  // Initialization with a persistent loaded object.
+  //
+  template <class Y> lazy_ptr (database&, Y*);
+  template <class Y> lazy_ptr (database&, const eager_ptr<Y>&);
+
+  template <class Y> void reset (database&, Y*);
+  template <class Y> void reset (database&, const eager_ptr<Y>&);
+
+  // Initialization with a persistent unloaded object.
+  //
+  template <class ID> lazy_ptr (database&, const ID&);
+
+  template <class ID> void reset (database&, const ID&);
+
+  // Query object id and database of a persistent object.
+  //
+  template <class O /* = T */>
+  object_traits<O>::id_type object_id () const;
+
+  odb::database& database () const;
+};
+  
+ +

In a lazy weak pointer interface, the load() function + returns the strong (shared) eager pointer. The following + transaction demonstrates the use of a lazy weak pointer based on + the employer and employee classes + presented earlier.

+ +
+typedef std::vector<lazy_weak_ptr<employee> > employees;
+
+session s;
+transaction t (db.begin ());
+
+shared_ptr<employer> er (db.load<employer> ("Example Inc"));
+employees& es (er->employees ());
+
+for (employees::iterator i (es.begin ()); i != es.end (); ++i)
+{
+  // We are only interested in employees with object id less than
+  // 100.
+  //
+  lazy_weak_ptr<employee>& lwp (*i);
+
+  if (lwp.object_id<employee> () < 100)
+  {
+    shared_ptr<employee> e (lwp.load ()); // Load and lock.
+    cout << e->first_ << " " << e->last_ << endl;
+  }
+}
+
+t.commit ();
+  
+ +

Notice that inside the for-loop we use a reference to the lazy + weak pointer instead of making a copy. This is not merely to + avoid a copy. When a lazy pointer is loaded, all other lazy + pointers that point to the same object do not automatically + become loaded (though an attempt to load such copies will + result in them pointing to the same object, provided the + same session is still in effect). By using a reference + in the above transaction we make sure that we load the + pointer that is contained in the employer + object. This way, if we later need to re-examine this + employee object, the pointer will already + be loaded.

+ +

As another example, suppose we want to add an employee + to Example Inc. The straightforward implementation of this + transaction is presented below:

+ +
+session s;
+transaction t (db.begin ());
+
+shared_ptr<employer> er (db.load<employer> ("Example Inc"));
+shared_ptr<employee> e (new employee ("John", "Doe"));
+
+e->employer_ = er;
+er->employees ().push_back (e);
+
+db.persist (e);
+t.commit ();
+  
+ +

Notice here that we didn't have to update the employer object + in the database since the employees_ list of + pointers is an inverse side of a bidirectional relationship + and is effectively read-only, from the persistence point of + view.

+ +

A faster implementation of this transaction, that avoids loading + the employer object, relies on the ability to initialize an + unloaded lazy pointer with the database where the object + is stored as well as its identifier:

+ +
+lazy_shared_ptr<employer> er (db, std::string ("Example Inc"));
+shared_ptr<employee> e (new employee ("John", "Doe"));
+
+e->employer_ = er;
+
+session s;
+transaction t (db.begin ());
+
+db.persist (e);
+
+t.commit ();
+  
+ +

Y.4 Using Custom Smart Pointers

+ +

While the ODB runtime and profile libraries provide support for + the majority of widely-used pointers, it is also easy to add + support for a custom smart pointer.

+ +

To achieve this you will need to implement the + pointer_traits class template specialization for + your pointer. The first step is to determine the pointer kind + since the interface of the pointer_traits specialization + varies depending on the pointer kind. The supported pointer kinds + are: raw (raw pointer or equivalent, that is, unmanaged), + unique (smart pointer that doesn't support sharing), + shared (smart pointer that supports sharing), and + weak (weak counterpart of the shared pointer). Any of + these pointers can be lazy, which also affects the + interface of the pointer_traits specialization.

+ +

Once you have determined the pointer kind for your smart pointer, + use a specialization for one of the standard pointers found in + the common ODB runtime library (libodb) as a base + for your own implementation.

+ +

Once the pointer traits specialization is ready, you will need to + include it into the ODB compilation process using the + --odb-epilogue option and into the generated header + file with the --hxx-prologue option. As an example, + suppose we have the smart_ptr smart pointer for which + we have the traits specialization implemented in the + smart-ptr-traits.hxx file. Then, we can create an ODB + compiler options file for this pointer and save it to + smart-ptr.options:

+ +
+# Options file for smart_ptr.
+#
+--odb-epilogue '#include "smart-ptr-traits.hxx"'
+--hxx-prologue '#include "smart-ptr-traits.hxx"'
+  
+ +

Now, whenever we compile a header file that uses smart_ptr, + we can pass the following option to make sure it is recognized by the + ODB compiler as a smart pointer and the traits file is included in the + generated code:

+ +
+--options-file smart-ptr.options
+  
+ +

It is also possible to implement a lazy counterpart for your + smart pointer. The ODB runtime library provides a class template + that encapsulates the object id management and loading + functionality that is needed to implement a lazy pointer. All + you need to do is wrap it with an interface that mimics + your smart pointer. Using one of the existing lazy pointer + implementations (either from the ODB runtime library or one + of the profile libraries) as a base for your implementation + is the easiest way to get started.

+ + -- cgit v1.1