使用 unique_ptr 的智能观察者。






4.77/5 (13投票s)
observer_ptr<T>,一个智能观察者,保证始终有效或可为空。通过透明地利用 unique_ptr<T> 的自定义删除器功能来检测对象销毁。
背景, 介绍,
observable_unique_ptr,
observer_ptr,
enable_observable_this,
自由函数,
开销,
使用代码, 自定义删除器,
工作原理, 代码解析,
总结, 历史记录
背景
自从引入了 auto_ptr
和现在的 unique_ptr
以来,C++ 中就不再需要内存泄漏,也不需要动态创建对象的首要所有权引用永远悬空。但是,对于不拥有对象而只是指向它的次要引用或别名,仍然存在一个问题。标准库提供了 weak_ptr
,它是指向 shared_ptr
所拥有对象的次要观察引用,但从未提供一个安全的观察智能指针来与 unique_ptr
等单个所有者一起使用。
当您代码的一部分需要引用已被另一部分代码拥有的动态对象时,就会存在观察引用。如果您使用 unique_ptr
来拥有您的对象,那么任何次要引用都必须以原始指针的形式持有...
struct SomeObjectType
{
A* m_pA; //reference to object of type A that will be owned elsewhere by unique_ptr
//.......
//........
};
SomeObjectType SomeObject;
unique_ptr
的 .get()
方法来初始化它以指向您的对象unique_ptr<A> apA(new A)
SomeObject.m_pA = apA.get();
现在,在不得不声明一个原始指针并通过调用其 .get()
方法来破坏我们 unique_ptr
的安全性后,我们非常清楚自己已经离开了智能指针安全区域,并可能陷入麻烦。我们知道不要删除 SomeObject
的 m_pA
成员,但我们能指望 m_pA
保持有效,或者至少在它无效时能测试为 null 吗?
一般的答案是“否”。以下操作将导致崩溃
unique_ptr<A> apA(new A);
SomeObject.m_pA = apA.get();
apA = NULL; //deletes the object
if(SomeObject.m_pA) //invalid and still non-null
SomeObject.m_pA->DoSomething(); //Crash
SomeObject
的 m_pA
成员只是一个变量,它存储您写入的任何指针值。它仍然是已删除对象的老地址。它非空,因此它会通过 if(SomeObject.m_pA)
测试,并尝试在无效内存上执行 DoSomething()
。如果它很快崩溃,您就该庆幸了。
没有人喜欢这种情况发生,因此总是会采取措施来防止它。这些措施包括设置结构限制以禁止某些操作(例如,在上述情况下,禁止删除对象)或创建复杂且通常不稳定的体系结构来管理允许的操作(例如,强制所有观察者注册到一个列表,该列表将在对象删除时用于将其归零)。这两种解决方案都不是理想的。
理想的解决方案是拥有一个智能观察指针,它足够智能,可以在不再有效时测试为零,而这正是这里提供的 observer_ptr<T>
。
引言
observer_ptr
不是零开销的。它背后有一个机制,该机制要求拥有 unique_ptr
在对象被删除时通知它。这是通过在此处提供的 **observable<T>
** 自定义伪删除器声明 unique_ptr
来实现的
//declaration of a unique_ptr that will be observed
unique_ptr<A, observable<A>> apA(new A);
或者使用此处也提供的 using 指令...
template <class T, class D = std::default_delete<T>>
using observable_unique_ptr = unique_ptr < T, observable<T, D> > ;
observable_unique_ptr<A> apA(new A);
现在可以如下观察 apA
observer_ptr<A> rA = apA;
因此,回到 背景部分描述的危险场景,但使用 observer_ptr
代替原始指针,使用 observable_unique_ptr
代替普通 unique_ptr
struct SomeObjectType
{
observer_ptr<A> m_rA; //reference to object of type A that will be owned elsewhere by unique_ptr
//.......
//........
};
SomeObjectType SomeObject;
observable_unique_ptr<A> apA(new A); SomeObject.m_rA = apA; apA = NULL; //deletes the object if(SomeObject.m_rA) //tests as zero because it is informed about the deletion SomeObject.m_rA->DoSomething(); //doesn't get called
observer_ptr
的非常具体的保证是,如果对象已被删除,它将测试为零。因此,我们只需要在使用 observer_ptr
之前进行测试。在这种情况下,它将测试为零,而 DoSomething()
将不会被调用。无需限制允许的操作,也无需创建复杂的体系结构来通知所有观察者。您可能已经注意到,observer_ptr
m_rA
的初始化是从所有者 pA
直接赋值的,没有调用 .get()
。这是因为使用 observer_ptr
时,我们永远不会离开智能指针安全区域。
您还可以使用一个 observer_ptr 来初始化另一个...
SomeOtherObject.m_rA = SomeObject.m_rA;
...并且您可以轻松地在代码中大量使用 observer_ptr
,而不会产生任何维护负担。只需在使用前测试它们。您不这样使用原始指针别名,因为保持所有观察者的最新状态将是一场噩梦。
observable_unique_ptr
observable_unique_ptr<T, D=default_delete<T>>
是...的一个 typedef 缩写(using 指令)
unique_ptr<T, observable<T, D=default_delete<T>>>
这是一个 unique_ptr
,通过声明它具有 observable<T, D=default_delete>
伪删除器来准备被 observer_ptr<T>
观察。这不会执行删除,它只是检测到删除以便通知隐藏的观察者基础结构。它调用传入的删除器 D
,默认情况下是 default_delete<T>
,来执行删除。
observable
伪删除器会产生一个指针变量的开销,使 observable_unique_ptr
的大小立即是未观察的 unique_ptr
的两倍。
observable_unique_ptr 的行为与 unique_ptr
完全相同,但是,与任何自定义删除器的 unique_ptr
一样,它不能用 make_unique<T>()
初始化。而是必须使用 make_observable<T>()
。
make_observable - 自由函数
observable_unique_ptr<T>
make_observable<T>(...)
。
返回一个 observable_unique_ptr
,它拥有一个类型为 T
的新对象。
observable_unique_ptr<T> = make_observable<T>();
如果您想使用自己的自定义删除器而不是 default_delete
,那么您将不得不编写自己的可观察的 make 函数来与其配合 - 请参阅自定义删除器部分。
observer_ptr
observer_ptr<T>
是这里提供的一个全新的智能指针。
它是 observable_unique_ptr<T>
持有对象的智能观察者。其主要特征是:
- 它不能用来删除它引用的对象
- 如果对象已被其所有者删除,它将测试为零。
observer_ptr
持有它引用的对象的指针,以及指向单独的有效性指示器的进一步指针,使其大小立即是原始指针的两倍。
构造和赋值
observer_ptr
可以未初始化地构造。也就是说,读取为 NULL
。
observer_ptr<A> rA;
并且可以从以下项构造或赋值:
- 一个
observable_unique_ptr
- 另一个
observer_ptr
- 或
NULL
observable_unique_ptr<A> apA=make_observable<A>(); rA = apA; //from an observable_unique_ptr observer_ptr<A> rA2 = rA; //from another observer_ptr rA = NULL; //from NULL - stops observing the object
它不能从以下项赋值或构造:
- 一个原始指针
- 或
make_observable<T>()
或make_unique<T>()
rA = new A; //ERROR will not compile
rA = make_observable<A>(); //ERROR will not compile
唯一可以从 observer_ptr
构造的智能指针是另一个 observer_ptr
。您不能从 observer_ptr
构造 observable_unique_ptr
。
observable_unique_ptr<T> apT = make_observable<T>(); observer_ptr<T> rT = apT; observable_unique_ptr<T> apT2 = rT; //ERROR will not compile
交互语法
这些直接赋值规则在 observer_ptr
和 observable_unique_ptr
之间创建了一个编译器强制的交互语法,确保您必须付出相当大的努力才能错误地使用它们。因为它基于仅促进正确移动的直接赋值,所以它导致最容易编写的代码是正确的代码。您只能自然而然地做对。
点方法
T* get()
返回被指向者作为原始指针void release()
作为将观察者归零的赋值为 NULL 的替代方法。
enable_observable_this
enable_observable_this
是一个附加的基类,它提供了一个方法来返回一个引用“this
”指针的 observer_ptr
,该方法可以从类定义内部调用。
它侵入了您的类定义,因此不是使对象可被 observer_ptr
观察的首选方式。尽管如此,对于类来说,它可能是封装外部观察引用初始化的最佳方式,因为其正常运行可能需要这些引用。
get_observer_of_this 方法
observer_ptr<T> get_observer_of_this(this)
返回一个引用 this
指针的 observer_ptr
。
class MyClass : public enable_observable_this
{
MyClass(observer_ptr<MyClass>& obs) //initialisation in constructor
{
obs = get_observer_of_this(this);
}
};
请注意,在上面的示例中,get_observer_of_this(this)
已在类的构造函数中调用,这是确保正确初始化的自然位置。但是,如果您熟悉共享所有权等效项 std::enable_shared_from_this
,您将知道在类的构造函数中调用其 shared_from_this()
方法将抛出运行时异常。这是因为 shared_from_this()
在运行时检查对象是否被正确拥有,而构造发生在确定所有权之前。
get_observer_of_this(this)
永远不会抛出异常,并且即使在构造函数中调用,也始终返回一个有效的 observer_ptr
。这是因为正确的对象所有权是通过以下限制在编译时确保的:继承 enable_observable_this
的类型只能以某种方式创建和拥有。
对象创建和所有权
继承 enable_observable_this
的类的对象只能由 make_observable<T>()
或自定义删除器等效项创建,并且只能由 observable_unique_ptr<U>
拥有,其中 U
也继承 enable_observable_this。
这确保在编译时不会以可能使 this
指针不安全进行观察的方式创建或拥有它们。这意味着您将无法以任何其他方式声明它们为已拥有,甚至不能声明为静态变量。
class A : public enable_observable_this
{
};
A a; //ERROR - will not compile
observable_unique_ptr<A> apA = make_observable<A>(); //OK
多态所有权
enable_observable_this
不接受类型模板,可以添加到类层次结构中的任何一点,但由于所有权规则,您将无法将所有权从继承 enable_observable_this
的类型转移到不继承 enable_observable_this
的基类。
class A
{};
class B : public A, public enable_observable_this
{};
observable_unique_ptr<A> apA = make_observable<B>(); //ERROR will not compile
对于多态所有权的影响是,继承 enable_observable_this
的类的通用拥有基类也必须继承 enable_observable_this
,即使它本身不调用 get_observer_of_this(this)
。
总结:
在层次结构中继承 enable_observable_this
的点(如果您使用它)应该是您用于多态所有权的通用基类。
zero_observers 方法
为了完整性,还提供了一个方法来从继承 enable_observable_this
的类的定义内部显式地将所有观察者归零。
void zero_observers(
)
这将归零从持有对象的 observable_unique_ptr
外部获取的任何观察者。
自由函数
make_observable - 如上所述
observable_unique_ptr<T>
make_observable<T>(...)
。
提供了一个函数,用于希望显式归零引用 observable_unique_ptr
的所有 observer_ptr 的情况。其参数必须是所有者,观察者无法做到这一点。
void zero_observers(
observable_unique_ptr<T>& ptr)
之所以提供它,是因为在某些情况下您可能希望这样做,特别是在转移所有权时,这可能会将对象从其被观察的上下文中移除。一个非常重要的例子是当您希望将对象转移到另一个线程进行处理时。这通常使用 std::swap()
完成。使用 observable_unique_ptr
,您应该确保不会在线程之间保留观察者,如下所示:
zero_observers(main_thread_ptr);
zero_observers(worker_thread_ptr);
swap(main_thread_ptr, worker_thread_ptr);
您将无法使用 std::move()
将所有权从 observable_unique_ptr
转移到非观察式 unique_ptr
,因为这可能导致观察者被遗弃且不安全。为此,您需要使用
unique_ptr<T>
move_to_unobservable(observable_unique_ptr<T>& ptr
)
它将确保在转移所有权之前,任何引用它的 observer_ptr
都被归零。
observable_owner_ptr<T> apT(new T); observer_ptr<T> rT=apT; unique_ptr<T> apT2 = move_to_unobservable(apT); //zeroes all observers because apT2 can't support them if(rT) //tests as zero rT->DoSomething();
对于继承 enable_observable_this
的类型,转移到 unique_ptr
是不允许的,即使使用 move_to_unobservable()
也不行 - 请参阅 enable_observable_this 部分。
static_pointer_cast
为了完整性,提供了一个静态转换函数,用于显式地将 observer_ptr
转换为更派生类的指针。
observer_ptr<more_derived_class> static_pointer_cast<more_derived_class> (observer_ptr<less_derived_class>& ptr)
从更派生到更不派生的转换是隐式进行的,不需要显式转换。
开销
如前所述,observer_ptr
和 observable_unique_ptr
的大小立即是原始指针的两倍。
最小大小 = 2 个原始指针
此外,observable_unique_ptr
和所有引用它的 observer_ptr
通过一个单独分配的 **DWORD
** 堆内存连接在一起。
典型平均大小 = 2 到 2.5 个原始指针之间
单独分配的 DWORD
堆内存可能会附着在已删除对象的 observer_ptr
和不再有任何观察者的 observable_unique_ptr
上。
最坏情况最大大小 = 3 个原始指针
使用...
enable_observable_this
强制您的类拥有一个虚函数表
...如果它还没有的话。
使用中有一个少量的代码执行,但其中没有涉及迭代、递归或数组/列表的遍历。这一切都相当直接。单独分配的 DWORD
的堆分配也可能很快返回 - 一个 DWORD
应该不难找到。
使用代码
要使用 observer_ptr
,只需包含下载的文件 observer_ptr.h
#include <memory> //for unique_ptr
#include "observer_ptr.h"
它没有包含在命名空间中。如果您觉得有必要或有帮助,您可以自己封装。
#include <memory> //for unique_ptr
namespace xnr{
#include "observer_ptr.h"
}
我建议您编写一些实验性代码来尝试一下,然后看看如何在您的代码中应用它。
处理您已知的观察者
您可以从处理您代码中已知的观察者开始,因为您已经限制了允许的操作以避免破坏它们,或者您已经编写了代码来使其保持对删除的最新了解。将它们从原始指针转换为 observer_ptr
。这将强制您将所有者从 unique_ptr
转换为 observable_unique_ptr
。现在您可以删除那些关于允许操作的任意限制,和/或删除所有试图让观察者了解删除的支持代码。
寻找您不知道的那些
接下来,您可以查找您可能不知道的观察者并进行相同的操作。任何 全局 或 类成员 是 原始指针 的都是候选。您可能已经确保它们不会被意外捕获,但也可能只是侥幸避免了灾难。其中一些原始指针可能是对父类或静态同级的反向引用,在这种情况下,您应该可以使用 C++ 引用来代替,这将澄清情况并消除它们。
注意。请参阅下面的关于函数参数和局部变量是原始指针的讨论。
在您的设计中更广泛地使用观察者。
最后,您可以更改您的设计方法,以便更广泛地使用无麻烦的观察引用,即 observer_ptr 允许一个事物直接引用另一个事物是一个有用的构造。我们不应该因此而退缩。
函数和方法参数
在函数或方法调用期间,任何传入的对象都不能被删除,除非是通过调用本身发起的动作。在大多数情况下,这意味着不需要将其作为 observer_ptr
传递。 原始指针或引用足够好,并且不会创建不必要的与智能指针系统的链接。
两个例外是:
- 调用中发起的某个操作有可能删除传入的对象。
- 调用的目的是设置一个
observer_ptr
来指向传入的对象。
在这些情况下,函数应通过值接受该对象作为 observer_ptr
。 这将允许它被调用,传递所有者(unique_ptr
)或其观察者(observer_ptr
)。
void MyFunc(observer_ptr<T> rT);
observable_unique_ptr<T> apT = make_observable<T>();
observer_ptr<T> rT = apT;
MyFunc(apT); //OK
MyFunc(rT); //OK
这是一个函数示例,该函数可以发起导致传入对象删除的操作,并且由于使用了 observer_ptr 而避免了灾难。
void ShootInFoot(observer_ptr<Foot> rFoot)
{
if(rFoot)
rFoot->DoSomething():
MSG msg;
if(PeekMessage(&msg, NULL, NULL, NULL, PM_NOREMOVE)
DispatchMessage(&msg); //Foot shooting may happen here
if(rFoot) //check we still have foot
rFoot->DoSomethingElse();
}
局部变量(原始指针或引用)
局部变量(原始指针或 C++ 引用)与传递给函数的参数类似,因为它们指向的对象通常在其生命周期内都保证存在。在这些情况下使用 observer_ptr
是不合理的。但是,在使用本地变量引用集合元素时需要特别小心。以下是一种非常高效地处理集合元素的方式。
vector<T> v;
//fill vector
//...
T& t=v[3];
t.DoSomething();
t.DoSomethingElse();
但是以下代码不安全
T& t=v[3];
t.DoSomething();
t.DoSomethingElse();
v.push_back();
t.DoSomeMore();
向集合添加新元素可能会导致整个集合被重新分配到别处,从而使 t
无效。使用指针而不是并进行测试将无济于事,因为测试它会给出错误的结果。
T* pT=v[3];
pT->DoSomething();
pT->DoSomethingElse();
v.push_back();
if(pT) //unreliable test - pT could be invalid and still non-zero
pT->DoSomeMore(); //CRASH
这可以使用 observer_ptr
来解决,但这要求您的集合持有对象的 observable_unique_ptr
。所以我们可以这样重写:
vector<observable_unique_ptr<T>> v;
//fill vector
//...
observer_ptr<T> rT=v[3];
rT->DoSomething();
rT->DoSomethingElse();
v.push_back();
if(rT) //references the object which has not moved
rT->DoSomeMore(); //ok
这是一个非常有用且安全的解决方案。如果 vector 将其元素移到了新的地址,那么 rT
仍然有效,因为它是一个引用 observable_unique_ptr
所指向的对象(对象本身没有移动),而不是 observable_unique_ptr
本身(它可能已经移动)。此外,如果执行了导致其引用的元素被删除的操作,那么 rT
将测试为 null。
然而,这个解决方案迫使您改变了对象的保存方式,增加了开销,并使您的代码更丑陋。在某些情况下,您可能更喜欢通过更严格地限定局部指针和引用的使用范围来解决问题,使它们无法超出其安全的代码范围。
vector<T> v;
//fill vector
//...
{ //new scope
T& t=v[3]; //initialise within tight scope
t.DoSomething();
t.DoSomethingElse();
} //close scope before performing operation on collection
v.push_back(); //array may move but its v[3] element has not been removed
{ //new scope
T& t=v[3]; //initialise new reference within tight scope
t.DoSomeMore();
} //close scope to prevent any leakage of reference t into further code
在尝试用 observer_ptr
替换局部变量(原始指针或引用)之前,您应该尝试将这些局部变量的作用域限制得更紧,以至于它们不能在无效时持续存在。
自定义删除器
如果您想提供自己的自定义删除器,您可以将其作为第二个模板参数传递给 observable_unique_ptr
,就像您对 unique_ptr
一样。
observable_unique_ptr<T, my_deleter<T>> apT(my_get_new_object<T>());
这(通过 using
指令)会展开为...
unique_ptr<T, observable<T, my_deleter<T>>> apT(my_get_new_object<T>());
自定义 make 函数
您将无法使用 make_observable<T>()
来初始化它,并且必须编写自己的 make 函数,该函数以与您的自定义删除器兼容的方式创建对象。
template<class T, class... _Types>
inline observable_unique_ptr<T, my_deleter<T>>
make_observable_my_way(_Types&&... _Args)
{
return observable_unique_ptr<T, my_deleter<T>>
(
my_get_new_object<T> (_STD forward<_Types>(_Args)...)
);
}
适应继承 enable_observable_this 的类型
如果您想使用自定义删除器与继承 enable_observable_this
的类配合使用,那么您还需要进行另外两步操作:
- 将对象创建函数传递的类类型
T
,改为传递其超类to_be_held_by_observable_unique<T>
。
template<class T, class... _Types>
inline observable_unique_ptr<T, my_deleter<T>>
make_observable_my_way(_Types&&... _Args)
{
return observable_unique_ptr<T, my_deleter<T>>
(
my_get_new_object<to_be_held_by_observable_unique<T>> (_STD forward<_Types>(_Args)...)
);
}
- 通过定义
FRIENDS_OF_HELD_BY_OBSERVABLE_UNIQUE
宏,在包含 oberver_ptr.h 之前,将您的创建函数注册为to_be_held_by_observable_unique<T>
的友元,如下所示:
#define FRIENDS_OF_HELD_BY_OBSERVABLE_UNIQUE \ template<class T, class... _Types> friend T* my_get_new_object(_Types&&... _Args); #include "observer_ptr.h"
如果您想使用多个自定义删除器,只需扩展友元列表...
#define FRIENDS_OF_HELD_BY_OBSERVABLE_UNIQUE \ template<class T, class... _Types> friend T* my_get_new_object(_Types&&... _Args); \ template<class T, class... _Types> friend T* my_other_get_new_object(_Types&&... _Args); #include "observer_ptr.h"
to_be_held_by_observable_unique<T>
的应用可以在任何能够调用对象创建函数的调用堆栈中的任何点进行。但是,被声明为 to_be_held_by_observable_unique<T>
友元的函数必须是直接创建对象并调用其构造函数的函数。即使这些函数深埋在只读库代码中,您也可以这样声明它们为友元,而无需修改库代码。
工作原理
机制
observer_ptr
持有一个指向其引用的对象的指针,以及一个指向单独分配的 ref_counted_validity_flag
(1 DWORD
内存)的指针,该指针指示对象的有效性。安装在 observable_unique_ptr
中的 observable
删除器也持有一个指向同一标志的指针。也就是说,所有者和它的所有观察者都指向同一个 ref_counted_validity_flag
。基本机制是所有者在删除对象时将标志标记为无效,而观察者在访问被指向者之前检查其有效性。
ref_counted_validity_flag
的最高位同时指示有效性(如果设置则有效)以及它是否被所有者引用。其余位是引用它的观察者数量的计数。
ref_counted_validity_flag
必须在内存中保留,只要任何东西可能仍然引用它,并且可能在对象及其所有者超出范围后很长时间才需要。因此,它不会被所有者销毁,而是当指向它的引用计数降至零时,它会自行销毁。
在下面的图表中,一个对象已被创建并由 observable_unique_ptr
拥有。这设置了 unique_ptr
的“指向对象”成员,称为 pT
。可观察的删除器的指向 ref_counted_validity_flag
的指针成员,pRC
,最初保持为 NULL
。
当第一个 observer_ptr
被初始化以指向所有者时,它的 pT
和 pRC
成员会从所有者的成员中初始化,但如果所有者的 pRC
成员是 NULL
,则会为所有者和观察者的 pRC
成员创建一个 ref_counted_validity_flag
来指向。ref_counted_validity_flag
的初始值为 0x80000001 - 最高位设置,观察者计数为 1。
下一个引用所有者的观察者将增加观察者计数,使值为 0x80000002,如图所示。图表显示了对象生命周期内的稳定状态。当使用 observer_ptr
时,pRC
成员用于检查它指向的 ref_counted_validity_flag
的最高位。它发现该位已设置,因此返回被指向者(其 pT
成员)以供使用。
下一个图表显示了对象删除时发生的情况。unique_ptr
调用 observable
删除器来执行删除,它通过调用传入的删除器(默认为default_delete
)来完成,但在此之前,它使用其 pRC
成员来取消设置 ref_counted_validity_flag
的最高位,然后将 pRC
成员归零,从而将其与 ref_counted_validity_flag
断开连接。现在,即使 observable_unique_ptr
超出范围也无妨。observer_ptrs 本身没有改变,只是它们的 pRC
成员现在指向一个已标记为无效的 ref_counted_validity_flag
。这将指示 observer_ptr
在下次使用时忽略其 pT
成员。
当 observer_ptr
的其中一个被测试时(它们的所有使用都首先涉及测试),它将首先使用其 pRC
成员来测试 ref_counted_validity_flag
。当它发现该标志无效时,它会将其观察者计数减一,并将 pRC
成员归零(将其与 ref_counted_validity_flag 断开连接),最后返回 NULL
。从此以后,该 observer_ptr
将简单地测试为 null,因为其 pRC
成员为 null。无需将其 pT
成员归零,因为 NULL pRC
将确保 pT
永远不会被访问。
当最后一个 observer_ptr
被测试时,它在 ref_counted_validity_flag
中对观察者计数的递减将其值减至零。这会导致 ref_counted_validity_flag
自我销毁。
现在所有智能指针都立即测试为 NULL,而无需引用任何 ref_counted_validity_flag
。
也有可能两个 observer_ptr
都被归零,而所有者仍然持有一个有效对象。这将使所有者指向一个不再需要 ref_counted_validity_flag
,除了所有者仍然指向它。
ref_counted_validity_flag
将在对象被删除时销毁,取消设置最高位会将复合计数减少到零。
设计考量
设计必须考虑到智能指针本身可能会在内存中移动,尽管它们指向的对象不会。这意味着 observable_unique_ptr
和 observer_ptr
可以持有指向其 ref_counted_validity_flag
的指针,但 ref_counted_validity_flag
不能持有指向 observable_unique_ptr
或 observer_ptr
的指针,如果智能指针在内存中移动,它们可能会失效。正是因为这个原因:
observer_ptr
不能直接被告知对象删除。相反,它们在下次使用时必须引用ref_counted_validity_flag
。observable_unique_ptr
不能直接被告知没有剩余的观察者并且ref_counted_validity_flag
不再需要。相反,ref_counted_validity_flag
必须保留在内存中,以便observable_unique_ptr
可以指向它,直到observable_unique_ptr
本身被归零。
enable_observable_this 的影响
enable_observable_this
附加基类也带有一个 pRC
成员,它可能指向 ref_counted_validity_flag
。这必须存在,以便 get_observer_of_this(this)
可以正确设置它返回的 observer_ptr
。复杂之处在于,任何继承 enable_observable_this
的类都只能由 observable_unique_ptr
持有,而后者也带有一个 pRC
成员。我们出现了重复。这不仅浪费内存,而且如果我们继续这样做,我们就必须制定策略来同步它们。
继承 enable_observable_this
的类无法访问其所有者的 pRC
成员,但所有者可以访问类对象的 pRC
成员,前提是它知道该类继承了 enable_observable_this
。
这个问题通过一些编译时类型选择来解决,该选择会为继承 enable_observable_this
的类创建一个不同版本的 observable
删除器,该删除器不带 pRC
成员,而是使用类对象的 pRC
成员。
有两个 observable
删除器的可能实现以及根据 T
的类型持有 ref_counted_validity_flag
指针的两种方式,这增加了代码的复杂性,但类型选择被充分封装,不会引起混淆,您将在后面的代码描述中看到这一点。
代码实现
我们可以大致从 observer_ptr.h 的顶部开始,然后向下阅读。最好与对 observer_ptr.h 的完整访问结合阅读,因为我在叙述中只重复小片段。为简洁起见,我将使用术语 observable_unique_ptr<T>
,尽管它直到文件末尾才被定义,并且在整个代码中都完整地表示为 unique_ptr<T, observable<T>>
。
以下每个标题都代表 observer_ptr.h 的一个部分。它们是:
observable deleter,
observer_ptr,
enable_observable_this,
public free functions,
observable_unique_ptr
//----------------------------------observable deleter---------------------------
该部分中的第一项是 _PRIVATE_observable
类,它将 observable
删除器的内部细节隐藏在公共接口之外。
这以 ref_counted_validity_flag
类的定义开始。它只包含一个 unsigned int
成员,其最高位表示有效性,其余位表示观察者计数。它具有以下公共方法以简化和控制其使用:
//sets top bit only
inline ref_counted_validity_flag()
: m_compound_count(1 << top_bit_power_of_2);
// tests if top bit is set
inline bool get_valid() const ;
//unsets top bit and self destructs if result is zero
void mark_invalid() ;
//increments observer count
inline void add_weak() ;
//decrements observer count and self destructs if result is zero
inline void release_weak() ;
在 _PRIVATE_observable
类中,还有两个 observable
删除器的替代定义:
observable_with_pRC<T, D>
,它处理正常类型(不继承enable_observable_this
)并具有m_pRC
成员。observable_for_observable_this<T, D>
,它处理继承enable_observable_this
的类型,并且没有数据成员。
observable
根据 T
的类型在 _PRIVATE_observable
类在公共命名空间中的结束处,通过 using
指令定义为其中之一。
//observable<T, D> - Chooses correct implementation of observable deleter for type T
template <class T, class D = default_delete<T>>
using
observable = typename conditional
<//condition
is_base_of < enable_observable_this, T >::value,
//type if true and type if false
_PRIVATE_observable::observable_for_observable_this<T, D>,
_PRIVATE_observable::observable_with_pRC<T, D>
> ::type;
删除时,unique_ptr
将调用 observable
删除器的 operator()(T* p)
方法。
observable_with_pRC
实现如下:
//Invalidate RC and call passed in deleter to do delete
void operator()(T* p)
{
if (m_pRC)
{
m_pRC->mark_invalid();
m_pRC = NULL;
}
D()(p);
}
- 而
observable_for_observable_this
实现如下:
//just calls passed in deleter to do delete
inline void operator()(T* p)
{
//Do nothing
D()(p); //just call the passed in deleter
}
observable
删除器的替代实现的其它需要评论的特性是:
- 它们都使用私有继承传入的删除器。
template <class T, class D = default_delete<T>>
struct observable_with_pRC
: private D //no implicit conversion to D
这会阻止从 observable<T, D>
到其基类 D<T>
的自动隐式转换,这反过来又阻止了所有权从 unique_ptr<T , obervable<T, D<T>>>
转移到 unique_ptr<T , D<T>>
。这是为了防止 std::move()
将所有权从一个可观察的所有者转移到一个不可观察的所有者,这会导致观察者被遗弃。它迫使您改用 move_to_unobservable()
。
observable_with_pRC
提供了一个接受类型D
(传入的删除器)作为参数的构造函数。
//conversion permits move from unique to observable_unique
inline observable_with_pRC(const D d) : m_pRC(NULL)
{}
这允许从 D<T>
到 observable<T, D>
的自动隐式转换,而这默认不会发生。它允许 std::move()
将所有权从一个非观察者所有者转移到一个观察者所有者,这不成问题,不会丢失信息。
- 它们都有一个多态构造函数。
//required for polymorphism
template<class U, class D2,
class = typename enable_if<
//check types are convertable
is_convertible<U *, T *>::value
//also check passed in deleters are convertable
&& is_convertible<D2, D >::value,
void>::type>
inline observable_with_pRC(const observable_with_pRC<U, D2>& odi)
: m_pRC(odi.m_pRC)
{}
删除器需要这个,并且可以在 default_delete
中找到。这里的区别在于它还必须检查传入的删除器(执行删除操作的)是否可转换。
同样,两个替代删除器 observable_with_pRC
和 observable_for_observable_this
之间没有任何关系,这确保了继承 enable_observable_this
的类与不继承的类之间永远不会有任何转换。
//------------------------observer_ptr-------------------------------
此部分也以私有类 _PRIVATE_observer_ptr
开始,以隐藏其内部细节。在此私有类中,有两个结构体 rc_source_in_observable_deleter
和 rc_source_in_observable_this
,它们提供了方法的替代实现,该方法返回对正在使用的 m_pRC
成员的引用,以及一个根据 T
的类型选择相应实现的函数。
//Alternative RC sources
struct rc_source_in_observable_deleter
{
template <class T, class D>
inline static ref_counted_validity_flag*& get_ref_pRC
(unique_ptr<T, observable<T, D> >const& ptr)
{
return ptr.get_deleter().m_pRC;
}
};
struct rc_source_in_observable_this
{
template <class T, class D>
inline static ref_counted_validity_flag*& get_ref_pRC
(unique_ptr<T, observable<T, D> >const& ptr)
{
return ptr->m_pRC;
}
};
//function to get RC source
template <class T, class D>
inline static ref_counted_validity_flag*& get_ref_pRC
(unique_ptr<T, observable<T, D> >const& ptr)
{
return typename conditional
< //condition
is_base_of < enable_observable_this, T >::value,
//type if true and type if false
rc_source_in_observable_this,
rc_source_in_observable_deleter
> ::type::get_ref_pRC<T, D>(ptr);
}
get_ref_pRC()
必须返回 m_pRC
的引用,因为可能需要更改其值。
当类类型不继承 enable_observable_this
时,
- 返回
observable
删除器的m_pRC
的引用ptr.get_deleter().m_pRC
。
但是当类类型继承 enable_observable_this
时,
- 使用类对象
ptr->m_pRC
的m_pRC
的引用。
此外,在 _PRIVATE_observer_ptr
中是 observer_ptr_base
,它是 observer_ptr<T>
的未模板化基类。它持有 observer_ptr<T>
的 m_pRC
成员,确保它被正确初始化和销毁,并提供一组与它相关的操作方法,这些方法可以由 observer_ptr<T>
调用。这些方法封装了对 ref_counted_validity_flag
方法的调用以及对 m_pRC
成员值的更改(由 get_ref_pRC()
选择和返回)。这减少了由 observer_ptr<T>
生成的模板化代码量(代码膨胀的后缀)。其方法是:
//Autonomous operations on its own ref_counted_validity_flag
inline observer_ptr_base() ;
inline ~observer_ptr_base();
//Called operations on its own ref_counted_validity_flag
void _release() const ;
bool _check_valid_ref() const;
//Called operations that accept another ref_counted_validity_flag
void _point_to_observable_owner
(ref_counted_validity_flag*& pRC_class);
void _point_to_ref
(ref_counted_validity_flag*& pRC_src);
observer_ptr_base
还提供了一个 null_ref_ptr
类的定义,该类只有它自己和 observer_ptr
知道。它在 observer_ptr
中用作构造函数和赋值为 NULL
的参数。
在 _PRIVATE_observer_ptr
类之后是公共定义的 observer_ptr<T>
。它持有一个 m_pT
成员(被指向者),并且还通过继承 observer_ptr_base
,它持有一个 m_pRC
成员。
赋予 observer_ptr
特殊品质的关键方法当然是布尔测试。
inline operator const bool() const
{
return _check_valid_ref();
}
以及解引用运算符。
T* const operator->() const //can throw exception
{
if (_check_valid_ref())
return m_pT;
throw _PRIVATE_observer_ptr::null_dereference_exception();
}
与此一致,比较方法调用其 get()
方法来访问被指向者。
inline T* get() const
{
return (_check_valid_ref()) ? m_pT : NULL;
}
构造和赋值方法决定了 observer_ptr
的行为,需要一些注释 - 为简洁起见,我们只看构造函数。
- 从
NULL
inline observer_ptr(null_ref_ptr* pNull)
: m_pT(NULL)
{}
您接受 NULL
的参数类型是什么?我们希望能够显式地将 observer_ptr
设为 null,但我们不希望能够用任何其他数字或任何其他指针值来初始化它。通过接受一个没有人能知道的类型的指针,null_ref_ptr
,可以传递给它的唯一可接受的值是 NULL
(唯一无类型的指针值)。
- 从另一个
observer_ptr
inline observer_ptr(observer_ptr const & ptr)
{
if (m_pT = ptr.get()) //assignment followed by test
_point_to_ref(ptr.m_pRC);
}
template <class U>
inline observer_ptr(observer_ptr<U> const & ptr)
{
if (m_pT = ptr.get()) //assignment followed by test
_point_to_ref(ptr.m_pRC);
}
这允许观察者从观察者那里扩散,这是您可以使用 observer_ptr
轻松做到的。第一个是复制构造函数,必须显式定义以防止编译器生成其默认值。第二个是多态的。
- 从
observable_unique_ptr
template <class U, class UDel = std::default_delete<U>>
inline observer_ptr
(unique_ptr<U, observable<U, UDel>>const& ptr)
{
if (m_pT = ptr.get()) //assignment followed by test
_point_to_observable_owner
(_PRIVATE_observer_ptr::get_ref_pRC<U, UDel>(ptr));
}
template <class U, class UDel>
observer_ptr(unique_ptr<U, UDel>const&& ptr) = delete;
这利用了 rValue 引用区分,与 unique_ptr
相同,但逻辑相反。您可以从 observable_unique_ptr
构造 observer_ptr
,但前提是它没有超出范围。删除的构造函数,仅接受 rValue 引用 &&
,将捕获超出范围的 observable_unique_ptr
。这允许 observer_ptr
由 observable_unique_ptr
初始化,但不能由函数(如 std::move()
和 make_observable<T>()
)返回的临时 observable_unique_ptr
初始化,这些函数正在转移所有权。
observer_ptr
还有一个私有构造函数,它只被其友元类 enable_observable_this
和友元函数 static_pointer_cast<T>()
调用。其目的是允许这些组件直接设置其 m_pT
和 m_pRC
成员。
inline observer_ptr(T* pT, ref_counted_validity_flag*& pRC)
: m_pT(pT)
{
_point_to_observable_owner(pRC);
}
//----------------enable_observable_this (intrusive)------------------------
在这种情况下,我们将跳过私有类 _PRIVATE_observable_this
,首先查看公共可用的 enable_observable_this
附加基类的定义。以下是其完整内容:
//Add on base class giving smart observer of the this pointer
class enable_observable_this
: protected _PRIVATE_observable_this
{
friend struct _PRIVATE_observer_ptr::rc_source_in_observable_this;
private:
mutable ref_counted_validity_flag* m_pRC;
//pure vf forces use of complete_observable_this<T> for creation
virtual void unused(hidden h) = 0;
protected:
inline enable_observable_this() : m_pRC(NULL)
{}
inline ~enable_observable_this()
{
if (m_pRC)
m_pRC->mark_invalid();
}
//methods only available within class definition
template <class U> observer_ptr<U> get_observer_of_this(U* const pThis)
{
if (NULL == m_pRC)
m_pRC = new ref_counted_validity_flag;
//calls observer_ptr private constructor
return observer_ptr<U>(static_cast<U*>(this), m_pRC);
}
void zero_observers()
{
if (m_pRC)
m_pRC->mark_invalid();
m_pRC = NULL;
}
};
它的操作很简单。它带有一个 m_pRC
成员(指向 ref_counted_validity_flag
)。get_observer_of_this()
创建一个 ref_counted_validity_flag
指向它(如果它尚不存在),并且析构函数将在存在的情况下将 ref_counted_validity_flag
标记为无效。还有一个方法可以在不删除对象的情况下将所有观察者归零。
它还有一个纯虚函数:
virtual void unused(hidden h) = 0;
这是强制执行继承 enable_observable_this
的类型所适用的创建和所有权限制的关键。声明了一个纯虚函数后,除非派生类提供了该函数的实现,否则无法实例化它。此外,您不能在从 enable_observable_this
派生的类中提供该函数,因为 hidden
是一个私有类型,您无法访问它。正是这一点迫使您将继承 enable_observable_this
的类型超类化为 to_be_held_by_observable_unique<T>
用于对象创建。
to_be_held_by_observable_unique<T>
定义在 enable_observable_this
正下方。
//to_be_held_by_observable_unique<T> - Superclass for object creation
template <class T>
using to_be_held_by_observable_unique =
typename conditional
< //condition
is_base_of < enable_observable_this, T >::value
&&
!is_base_of //guard against multiple application
<_PRIVATE_observable_this::complete_observable_this<T>, T>
::value,
//type if true and type if false
_PRIVATE_observable_this::complete_observable_this<T>,
T //has no effect on types not inheriting enable_observable_this
> ::type;
它的定义是条件性的,取决于 T
的类型。如果该类型继承 enable_observable_this
,则该类型定义为 complete_observable_this<T>
,定义在 _PRIVATE_observable_this
类中,否则它简单地定义为 T
。
_PRIVATE_observable_this
类隐藏了私有 hidden
结构体的定义。
struct hidden{};
以及 complete_observable_this<T>
类。
//to_be_held_by_observable_unique<T> selects this for enable_observable_this types
template <class T> class complete_observable_this
: public T
{
//implementation of pure vf declared in enable_observable_this
void unused(hidden h)
{}
//friend functions
template<class T,
class... _Types>
friend typename enable_if < !is_array<T>::value,
unique_ptr<T, observable<T>> > ::type make_observable
(_Types&&... _Args);
template<class T>
friend typename enable_if<is_array<T>::value && extent<T>::value == 0,
unique_ptr<T, observable<T>> >::type make_observable
(size_t _Size);
FRIENDS_OF_HELD_BY_OBSERVABLE_UNIQUE
//private constructor can only be called by friends
template<class... _Types>
complete_observable_this(_Types&&... _Args)
: T(_STD forward<_Types>(_Args)...)
{}
};
它提供了 unused(hidden h)
纯虚函数的实现。
implementation of pure vf declared in enable_observable_this
void unused(hidden h)
{}
它什么也不做,也永远不会被调用,但它允许编译器实例化该类。
然后它定义了两个活动的 make_observable<T>()
形式作为友元,以及您在 FRIENDS_OF_HELD_BY_OBSERVABLE_UNIQUE
宏中定义的任何创建函数。这一点很重要,因为它的所有成员都是私有的,包括它的构造函数,所以只有这些友元函数才能创建该类型的对象。
//-------------------------public free functions---------------------------------------
make_observable<T>(...)
是基于 make_unique<T>(...
) 构建的,不同之处在于 new
传入的类型被 to_be_held_by_observable_unique<T>
超类化,并且它返回的是 observable_unique_ptr
而不是普通的 unique_ptr
。
//make_observable<T>() - adapted from make_unique<T>()
template<class T, class... _Types> inline
typename enable_if<!is_array<T>::value,
unique_ptr<T, observable<T>> >::type make_observable(_Types&&... _Args)
{
return unique_ptr<T, observable<T, default_delete<T>>>
(new to_be_held_by_observable_unique<T>(std::forward<_Types>(_Args)...));
}
zero_observers()
被声明为 _PRIVATE_observer_ptr
的友元,以便它可以使用其私有的 get_ref_pRC<T, D>(ptr)
函数。
//zero_observers() - Zeroes all observers of owner passed in template<class T, class D = std::default_delete<T>> void zero_observers (unique_ptr<T, observable<T, D> >& ptr) { if (_PRIVATE_observer_ptr::get_ref_pRC<T, D>(ptr)) { _PRIVATE_observer_ptr::get_ref_pRC<T, D>(ptr)->mark_invalid(); _PRIVATE_observer_ptr::get_ref_pRC<T, D>(ptr) = NULL; } }
move_to_unobservable()
对于继承 enable_observable_this
的类型是不可用的。它只是调用 zero_observers()
,然后从所有者释放的指针创建一个 unique_ptr
。
//move_to_unobservable() -Transfer from observable to non-observable
template<class T, class D = std::default_delete<T>>
//disallow if T inherits enable_observable_this
typename enable_if<!is_base_of<enable_observable_this, T>::value,
unique_ptr<T, D > >::type move_to_unobservable
(unique_ptr<T, observable<T, D> >& ptr)
{
zero_observers(ptr);
return unique_ptr<T, D >(ptr.release());
}
static_pointer_cast<T>()
如果 T
能够通过静态转换,那么它将编译成功。它被声明为 observer_ptr
的友元,并使用其私有构造函数来构成要返回的 observer_ptr
。
//static_pointer_cast() - Static cast function for observer_ptr<T>
template<class T, class U>
observer_ptr<T> static_pointer_cast(observer_ptr<U>& ptr)
{
//calls observer_ptr private constructor
return observer_ptr<T>(static_cast<T*>(ptr.m_pT), ptr.m_pRC);
}
//--------------------------------observable_unique_ptr-----------------------
最后,observable_unique_ptr
是通过 observer_ptr.h 末尾的 using
指令定义的,以澄清文件中没有任何其他内容使用此定义,并且您可以使用替代名称而不会破坏任何内容。
//observable_unique_ptr contraction defined here
template <class T, class D = std::default_delete<T>>
using
observable_unique_ptr = unique_ptr < T, observable<T, D> >;
摘要
observer_ptr
提供了简单可靠的观察引用(有效或 null),无需编写支持代码或避免对象删除场景。直接观察引用的使用有时是不可避免的,而 observer_ptr
提供了一种安全处理它们的简单解决方案。此外,其使用的简便性和内置安全性使得观察引用的广泛使用甚至扩散成为一种可行的设计选项。促进相关组件之间的直接引用可以提高代码的智能性和性能。对难以管理的原始指针别名的恐惧确实会让我们退缩,也许我们没有意识到。
std::unique_ptr
是高度发达且广受信赖的。通过 observable
删除器钩入其自定义删除器功能,使 observer_ptr
能够与之协同工作。使用 using
指令定义 observable_unique_ptr
确保您正在处理的是 unique_ptr
,而不是可能不完美地从其派生的类。通过 observable_
前缀使您的 unique_ptr
可观察,并使用 make_observable<T>()
初始化它们,您的 unique_ptr
不会因此而损坏。它们仍然是 unique_ptr
。
虽然您可以到处使用 observer_ptr
并且所有内容都将编译并正常工作,但 observer_ptr
及其配合使用的 observable_unique_ptr
确实会带来少量开销,并且在许多情况下,紧密作用域的局部原始指针和引用可能已经足够。
历史
首次发布和发布时间为 2015 年 9 月。许多概念都可以在以下先前发表的文章中找到先例,本文档已在 C++ 11 中取代它们:XONOR 指针:独占所有权和非独占引用指针 2008 年 5 月 14 日, 单所有者及其别名的智能指针 2014 年 4 月 14 日, 适用于大多数代码的合理智能指针封装 2014 年 4 月 30 日