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_ptr
和weak_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.SQL
和employee.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++11std::shared_ptr/weak_ptr
,ODB::tr1::lazy_shared_ptr/lazy_wiak_ptr
用于tr1std::tr1::shared_ptr/weak_ptr
,ODB::laxy_unique_ptr
用于C++11std::unique_ptr
,ODB::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运行时库或配置文件库之一)作为实现的基础是最简单的入门方法。