【开源库学习】libodb库学习(五)

6 关系

  • 持久对象之间的关系用指针或指针容器表示。ODB运行时库提供了对shared_ptr/weak_ptr(TR1或C++11)、std::unique_ptr(C++11),std::auto_ptr和原始指针的内置支持。此外,ODB配置文件库可用于常用的框架和库(如Boost和Qt),为这些框架和库中的智能指针提供支持(第三部分,“配置文件”)。添加对自定义智能指针的支持也很容易,如第6.5节“使用自定义智能指针”中稍后讨论的那样。任何支持的智能指针都可以在数据成员中使用,只要它可以从规范对象指针显式构造(第3.3节,“对象和视图指针”)。例如,如果对象指针是shared_ptr,我们可以使用weak_ptr

  • 当加载包含指向另一个对象的指针的对象时,指向的对象也会被加载。在某些情况下,这种急于加载关系是不可取的,因为它可能会导致大量未使用的对象从数据库中实例化。为了支持对关系加载的更精细控制,ODB运行时和配置文件库提供了所支持指针的所谓懒惰版本。加载包含对象时,惰性指针指向的对象不会自动加载。相反,我们必须明确地请求所指向对象的实例化。第6.4节“懒惰指针”详细讨论了懒惰指针。

  • 作为一个简单的例子,考虑以下雇员-雇主关系。本章中给出的代码示例将使用TR1(std::TR1)命名空间中的shared_ptrweak_ptr智能指针。

#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_;
};
  • 默认情况下,对象指针可以为NULL。为了指定指针始终指向有效对象,我们可以对单个对象指针使用not_null pragma(第14.4.6节,“null/not_null”),对对象指针的容器使用value_not_nul pragma。例如:
#pragma db object
class employee
{
  ...

  #pragma db not_null
  shared_ptr<employer> current_employer_;

  #pragma db value_not_null
  std::vector<shared_ptr<employer> > previous_employers_;
};
  • 在这种情况下,如果我们在employee对象上调用persist()update()数据库函数,并且current_employer_指针或存储在previous_employers_容器中的一个指针为NULL,则将抛出odb::NULL_pointer异常。

  • 我们不需要做任何特殊的事情来建立或导航两个持久对象之间的关系,如下面的代码片段所示:

// 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 ();
}
  • 上面代码中唯一值得注意的一行是在第二个事务开始之前创建会话。如第11章“会话”所述,会话充当持久对象的缓存。通过在加载employee对象之前创建会话,我们可以确保它们的employee_指针指向同一个employer对象。如果没有会话,每个员工最终都会指向Example Inc雇主自己的私人实例。

  • 作为一般准则,在加载具有指向其他持久对象的指针的对象时,您应该使用会话。会话确保对于给定的对象id,在与其相关的所有其他对象之间共享一个实例。

  • 我们还可以在数据库查询中使用指向对象的数据成员(第4章,“查询数据库”)。对于持久类中的每个指针,查询类定义了一个类似智能指针的成员,该成员包含与所指向对象中的数据成员相对应的成员。然后,我们可以通过指针语法(->)使用访问来引用指向对象中的数据成员。例如,employee对象的查询类包含employer成员(其名称来源于employer指针),而employer又包含name成员(其姓名来源于所指向对象的employer::name_ data成员)。因此,在查询数据库中的employee对象时,我们可以使用query::employer->name表达式。例如,以下事务可查找example Inc的姓为Doe的所有员工:

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 ();
  • 与non-inverse(第6.2节,“双向关系”)对象指针对应的查询类成员也可以用作具有所指向对象id类型的普通成员。例如,以下查询查找所有没有关联雇主对象的员工对象:
result r (db.query<employee> (query::employer.is_null ()));
  • 处理对象关系时要记住的一个重要概念是持久对象的独立性。特别是,当包含指向另一个对象的指针的对象被持久化或更新时,所指向的对象不会自动持久化或升级。相反,数据库中只为指向的对象存储了对对象的引用(以对象id的形式)。被指向的对象本身是一个独立的实体,应该被持久化或独立更新。默认情况下,相同的原则也适用于擦除指向的对象。也就是说,我们必须确保所有指向对象都相应地更新。然而,在擦除的情况下,我们可以在删除语义上指定一个替代方案,如第14.4.15节“n_delete”所述。

  • 当持久化或更新包含指向另一个对象的指针的对象时,指向的对象必须具有有效的对象id。然而,在涉及具有自动分配标识符的对象的复杂关系中,这可能并不总是容易实现的。在这种情况下,可能需要首先将指针设置为NULL的对象持久化,然后,一旦指向的对象被持久化并分配了标识符,将指针设置到正确的值并更新数据库中的对象。

  • 持久对象关系可分为两类:单向和双向。每个组又包含几个配置,这些配置根据关系各方的基数而有所不同。以下部分讨论了所有可能的单向和双向配置。

6.1 单向关系

  • 在单向关系中,我们只对在一个方向上从一个对象导航到另一个对象感兴趣。因为没有兴趣在相反的方向上导航,所以关系另一端的基数并不重要。因此,只有两种可能的单向关系:对一和对多。以下各节将详细描述这些关系。有关如何处理这些关系的示例代码,请参阅odb-examples中的关系示例。

6.1.1 To-One关系

  • 单向To-One关系的一个例子是雇员-雇主关系(雇员只有一个雇主)。以下持久C++类模拟了这种关系:
#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_;
};
  • 相应的数据库表如下所示:
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));

6.1.2 To-Many关系

  • 单向To-Many关系的一个例子是员工项目关系(一名员工可以参与多个项目)。以下持久C++类模拟了这种关系:
#pragma db object
class project
{
  ...

  #pragma db id
  std::string name_;
};

#pragma db object
class employee
{
  ...

  #pragma db id
  unsigned long id_;

  #pragma db value_not_null unordered
  std::vector<shared_ptr<project> > projects_;
};
  • 相应的数据库表如下所示:
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));
  • 为了获得更规范的数据库模式,可以使用ODB pragmas(第14章,“ODB Pragma语言”)自定义上述表和列的名称。例如:
#pragma db object
class employee
{
  ...

  #pragma db value_not_null unordered \
             id_column("employee_id") value_column("project_name")
  std::vector<shared_ptr<project> > projects_;
};
  • 生成的employee_projects表如下所示:
CREATE TABLE employee_projects (
  employee_id BIGINT UNSIGNED NOT NULL,
  project_name VARCHAR (255) NOT NULL REFERENCES project (name));

6.2 双向关系

  • 在双向关系中,我们感兴趣的是在两个方向上从一个对象导航到另一个对象。因此,关系中的每个对象类都包含指向另一个对象的指针。如果使用智能指针,则应使用弱指针作为指针之一,以避免所有权周期。例如:
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_;
};
  • 请注意,当我们建立双向关系时,我们必须一致地设置两个指针。确保关系始终处于一致状态的一种方法是提供一个同时更新两个指针的函数。例如:
#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_;
};
  • 在本章开始时,我们研究了如何使用会话来确保指向它的所有其他对象共享一个对象。由于涉及弱指针的双向关系,会话的使用变得更加重要。考虑以下事务,该事务试图在不使用会话的情况下从上述示例中加载position对象:
transaction t (db.begin ())
shared_ptr<position> p (db.load<position> (1));
...
t.commit ();
  • 当我们加载position对象时,它指向的employee对象也会被加载。虽然employee最初存储为shared_ptr,但它随后被分配给employee_成员weak_ptr。分配完成后,共享指针将超出作用域,指向新加载的employee对象的唯一指针是employee_弱指针。这意味着加载后立即删除employee对象。为了避免这种病态情况,ODB检测到新加载的对象将立即被删除的情况,并抛出ODB::session_required异常。

  • 正如异常名称所暗示的那样,解决此问题的最简单方法是使用会话:

session s;
transaction t (db.begin ())
shared_ptr<position> p (db.load<position> (1));
...
t.commit ();
  • 在我们的示例中,会话将维护一个指向已加载员工对象的共享指针,以防止其立即删除。解决此问题的另一种方法是避免使用惰性弱指针立即加载指向的对象。本章稍后的第6.4节“懒惰指针”将讨论懒惰指针。

  • 上面,为了在持久类中建模双向关系,我们使用了两个指针,每个对象一个。虽然这是C++中的自然表示,但它并不能转化为规范的关系模型。考虑为上述两个类生成的数据库模式:

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));
  • 虽然此数据库模式有效,但它是非常规的。我们有一个从职位表中的一行到员工表中一行的引用。我们还可以从employee表中的同一行引用回position表的行。从关系的角度来看,其中一个引用是多余的,因为在SQL中,我们只需使用一个引用就可以轻松地在两个方向上导航。

  • 为了消除冗余的数据库模式引用,我们可以使用反向杂注(第14.4.14节,“反向”),它告诉ODB编译器指针是双向关系的反向。关系的任何一方都可以颠倒过来。例如:

#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_;
};
  • 生成的数据库模式如下:
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));
  • 正如您所看到的,反向成员没有相应的列(或表,如果是指针的反向容器),从数据库操作的角度来看,它实际上是只读的。更改与反向侧的双向关系的唯一方法是设置其直接(非反向)指针。还要注意,作为双向关系反面的指针的有序容器(第5.1节,“有序容器”)总是被视为无序的(第14.4.19节,“无序的”),因为这种容器的内容是从不包含元素顺序(索引)的关系的直接侧隐式构建的。

  • 我们将在以下部分介绍三种不同的双向关系:一对一、一对多和多对多。我们将只讨论与反面的双向关系,因为它们会产生规范的数据库模式。有关如何处理这些关系的示例代码,请参阅odb-examples中的反向示例。

6.2.1 One-to-One关系

  • 上述员工-职位关系是双向一对一关系的一个例子(一名员工担任一个职位,一个职位由一名员工填补)。以下持久C++类模拟了这种关系:
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_;
};
  • 相应的数据库表如下所示:
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));
  • 如果相反,这种关系的另一端是相反的,那么数据库表将按如下方式更改:
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);

6.2.2 One-to-Many关系

  • 双向一对多关系的一个例子是雇主-雇员关系(雇主有多个雇员,雇员由一个雇主雇用)。以下持久C++类模拟了这种关系:
class employee;

#pragma db object
class employer
{
  ...

  #pragma db id
  std::string name_;

  #pragma db value_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_;
};
  • 相应的数据库表因关系的哪一侧被颠倒而存在显著差异。如果一方(雇主)与上述代码相反,则生成的数据库模式如下:
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));
  • 如果将此关系的多方(员工)颠倒过来,则数据库表将按如下方式更改:
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);

6.2.3 Many-to-Many关系

  • 双向多对多关系的一个例子是员工-项目关系(一个员工可以处理多个项目,一个项目可以有多个参与的员工)。以下持久C++类模拟了这种关系:
class employee;

#pragma db object
class project
{
  ...

  #pragma db id
  std::string name_;

  #pragma db value_not_null inverse(projects_)
  std::vector<weak_ptr<employee> > employees_;
};

#pragma db object
class employee
{
  ...

  #pragma db id
  unsigned long id_;

  #pragma db value_not_null unordered
  std::vector<shared_ptr<project> > projects_;
};
  • 相应的数据库表如下所示:
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));
  • 如果相反,这种关系的另一端是相反的,那么数据库表将按如下方式更改:
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);

6.3 循环关系

  • 如果两个持久类中的每一个都引用另一个,则它们之间的关系是循环的。双向关系总是循环的。与继承相结合的单向关系(第8章,“继承”)也可以是循环的。例如,employee类可以从person派生,而person又可以包含一个指向employee的指针。

  • 如果在同一个头文件中定义了具有循环依赖关系的持久类,我们不需要做任何额外的事情。具体来说,ODB将确保数据库表和外键约束以正确的顺序创建。因此,除非你有充分的理由不这样做,否则建议你在同一个头文件中保留具有循环依赖关系的持久类。

  • 如果你必须将这些类保存在单独的头文件中,那么为了在ODB中使用这些类,你可能需要采取两个额外的步骤。再次考虑第6.2.1节“一对一关系”中的示例,但这次是在单独的标头中定义的类:

// position.hxx
//
class employee;

#pragma db object
class position
{
  ...

  #pragma db id
  unsigned long id_;

  #pragma db inverse(position_)
  weak_ptr<employee> employee_;
};
  

// employee.hxx
//
#include "position.hxx"

#pragma db object
class employee
{
  ...

  #pragma db id
  unsigned long id_;

  #pragma db not_null
  shared_ptr<position> position_;
};
  • 请注意,position.hxx标头仅包含员工的正向声明。虽然这足以定义一个有效的位置类,但从C++的角度来看,ODB编译器需要“看到”指向持久类的定义。我们有几种方法可以满足这一要求。最简单的方法是在position.hxx的末尾添加employee.hxx
// position.hxx
//
class employee;

#pragma db object
class position
{
  ...
};

#include "employee.hxx"
  • 我们还可以将此包含限制为使用ODB编译器编译position.hxx的时间:
// position.hxx
//

...

#ifdef ODB_COMPILER
#  include "employee.hxx"
#endif
  • 最后,如果我们不想修改position.hxx,那么我们可以使用--ODB-epilogue选项将employee.hxx添加到ODB编译过程中。例如:
odb ... --odb-epilogue "#include \"employee.hxx\"" position.hxx
  • 还要注意,在这个例子中,我们不必为employee.hxx做任何额外的事情,因为它已经包含了position.hxx。然而,如果它只依赖于position类的前向声明,那么我们就必须以与position.hxx相同的方式处理它。

  • 涉及循环关系的单独定义类的另一个困难与生成的数据库模式中外键约束创建的正确顺序有关。在上面的示例中,如果我们将数据库模式生成为独立的SQL文件,那么我们最终将得到两个这样的文件:position.SQLemployee.SQL。如果我们尝试先执行employee.sql,那么我们将得到一个错误,表明与position类对应并由与position_指针对应的外键约束引用的表尚不存在。

  • 请注意,如果数据库模式嵌入在生成的C++代码中,而不是作为独立的SQL文件生成,则不会出现这样的问题。在这种情况下,即使类在单独的头文件中定义,ODB编译器也能够确保正确的创建顺序。

  • 在某些情况下,例如,与反面的双向关系,可以通过以正确的顺序执行数据库模式创建文件来解决此问题。在我们的示例中,这将是position.sql第一个,employee.sql第二个。然而,这种方法并不能扩展到简单的对象模型之外。

  • 这个问题的一个更稳健的解决方案是将所有持久类的数据库模式生成到一个SQL文件中。这样,ODB编译器可以再次确保表和外键的正确创建顺序。为了指示ODB编译器为多个标头生成组合模式文件,我们可以使用--generate schema only--at once选项。例如:

odb ... --generate-schema-only --at-once --input-name company \
position.hxx employee.hxx
  • 上述命令的结果是一个company.sql文件(名称来自--input name值),其中包含职位和员工类的数据库创建代码。

6.4 懒惰指针

  • 再次考虑本章前面介绍的双向一对多雇主-雇员关系:
class employee;

#pragma db object
class employer
{
  ...

  #pragma db id
  std::string name_;

  #pragma db value_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_;
};
  • 还要考虑以下事务,该事务根据员工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 ();
  • 虽然这个事务看起来很简单,但实际上它所做的远不止眼前所见,而且是必要的。考虑一下当我们加载employee对象时会发生什么:employee_指针也会自动加载,这意味着与此employee对应的employer对象也会被加载。但雇主对象又包含指向所有员工的指针列表,这些指针也会被加载。因此,当涉及对象关系时,像上面这样的简单事务可以加载比所需多得多的对象。

  • 为了克服这个问题,ODB以懒惰指针的形式对关系加载提供了更细粒度的控制。加载包含对象时,懒惰指针不会自动加载指向的对象。相反,当我们需要访问它时,我们必须显式地加载指向的对象。

  • ODB运行时库为所有支持的指针提供了惰性对应项,即:ODB::lazy_shared_ptr/lazy_weak_ptr用于C++11 std::shared_ptr/weak_ptrODB::tr1::lazy_shared_ptr/lazy_wiak_ptr用于tr1 std::tr1::shared_ptr/weak_ptrODB::laxy_unique_ptr用于C++11 std::unique_ptrODB::lazy_auto_ptr用于std::auto_ptr,以及ODB::lazy _ptr用于原始指针。TR1懒惰指针在<odb/TR1/lazy ptr.hxx>标头中定义,而所有其他指针都在<odb/lazy ptr.hxx>中定义。ODB配置文件库还为流行框架和库中的智能指针提供了懒惰指针实现(第三部分,“配置文件”)。

  • 虽然我们稍后将更详细地讨论懒惰指针的接口,但这些指针提供的最常用的额外函数是load()。如果指向的对象尚未加载,则此函数将加载该对象。调用此函数后,可以以与渴望指针相同的方式使用懒惰指针。load()函数还返回渴望指针,以防您需要传递它。对于懒惰的弱指针,load()函数也会锁定指针。

  • 以下示例显示了如何更改雇主-雇员关系以使用懒惰指针。在这里,我们选择对关系的双方都使用懒惰指针。

class employee;

#pragma db object
class employer
{
  ...

  #pragma db value_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_;
};
  • 事务的变化如下:
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 ();
  • 作为一般准则,我们建议你至少让双向关系的一方变得懒惰,尤其是对于与多方的关系。

  • 懒惰指针实现模仿了其热切对应物的接口,一旦加载指针,就可以使用该接口。它还添加了一些特定于延迟加载功能的附加函数。总的来说,懒惰指针的界面遵循以下总体轮廓:

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:
  //  NULL      loaded()
  //
  //  true       true      NULL pointer to transient object
  //  false      true      valid pointer to persistent object
  //  true       false     unloaded pointer to persistent object
  //  false      false     valid pointer to transient object
  //
  bool loaded () const;

  eager_ptr<T> load () const;

  // Unload the pointer. For transient objects this function is
  // equivalent to reset().
  //
  void unload () const;

  // Get the underlying eager pointer. If this is an unloaded pointer
  // to a persistent object, then the returned pointer will be NULL.
  //
  eager_ptr<T> get_eager () 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 */>
  // C++11: template <class O = T>
  object_traits<O>::id_type object_id () const;

  odb::database& database () const;
};
  • 在惰性弱指针接口中,load()函数返回强(共享)渴望指针。以下事务演示了基于前面介绍的雇主和雇员类的懒惰弱指针的使用。
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)
  // C++11: if (lwp.object_id () < 100)
  {
    shared_ptr<employee> e (lwp.load ()); // Load and lock.
    cout << e->first_ << " " << e->last_ << endl;
  }
}

t.commit ();
  • 请注意,在for循环中,我们使用对懒惰弱指针的引用,而不是复制。这不仅仅是为了避免复制。加载懒惰指针时,指向同一对象的所有其他懒惰指针不会自动加载(尽管尝试加载此类副本将导致它们指向同一个对象,前提是同一会话仍然有效)。通过在上述事务中使用引用,我们确保加载雇主对象中包含的指针。这样,如果我们以后需要重新检查此员工对象,指针将已经加载。

  • 作为另一个例子,假设我们想向example股份有限公司添加一名员工。下面介绍了该事务的直接实现:

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 ();
  • 请注意,我们不必更新数据库中的雇主对象,因为employees_list指针是双向关系的反面,从持久性的角度来看,它实际上是只读的。

  • 此事务的更快实现(避免加载雇主对象)依赖于使用存储对象的数据库及其标识符初始化卸载的懒惰指针的能力:

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 ();
  • 有关延迟指针与延迟加载对象部分的交互,请参阅第9.3节“部分和延迟指针”。

6.5 使用自定义智能指针

  • 虽然ODB运行时和配置文件库为大多数广泛使用的指针提供支持,但添加对自定义智能指针的支持也很容易。

  • 为了实现这一点,您需要为指针实现pointer_troperties类模板专门化。第一步是确定指针类型,因为pointer_troperties专门化的接口会根据指针类型而变化。支持的指针类型有:原始指针(原始指针或等效指针,即非托管指针)、唯一指针(不支持共享的智能指针)、共享指针(支持共享的智慧指针)和弱指针(共享指针的弱对应指针)。这些指针中的任何一个都可以是惰性的,这也会影响pointer_troperties专门化的接口。

  • 一旦确定了智能指针的指针类型,就可以使用公共ODB运行时库(libodb)中的一个标准指针的专门化作为您自己实现的基础。

  • 一旦指针特征专门化准备就绪,您将需要使用--ODB-epilogue选项将其包含在ODB编译过程中,并使用--hxx-prologue选项将其包括在生成的头文件中。例如,假设我们有smart_ptr智能指针,我们在smart-ptr-tracks.hxx文件中为其实现了特征专门化。然后,我们可以为此指针创建一个ODB编译器选项文件,并将其保存到smart-tr.options

# Options file for smart_ptr.
#
--odb-epilogue '#include "smart-ptr-traits.hxx"'
--hxx-prologue '#include "smart-ptr-traits.hxx"'
  • 现在,每当我们编译一个使用smart_str的头文件时,我们都可以指定以下命令行选项,以确保它被ODB编译器识别为智能指针,并且特征文件包含在生成的代码中:
--options-file smart-ptr.options
  • 也可以为你的智能指针实现一个懒惰的对应物。ODB运行时库提供了一个类模板,该模板封装了实现懒惰指针所需的对象id管理和加载功能。你所需要做的就是用一个模仿你的智能指针的界面来包装它。使用现有的懒惰指针实现之一(来自ODB运行时库或配置文件库之一)作为实现的基础是最简单的入门方法。
  • 16
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值