使用时似乎有一些边缘情况enabled_shared_from_this
。例如:
shared_from_this
不使用就可以实现吗enable_shared_from_this
?如果是这样,它可以做得那么快吗?
使用时似乎有一些边缘情况enabled_shared_from_this
。例如:
shared_from_this
不使用就可以实现吗enable_shared_from_this
?如果是这样,它可以做得那么快吗?
Ashared_ptr
是3样东西。它是一个引用计数器、一个销毁器和一个拥有的资源。
当 you 时make_shared
,它一次分配所有 3 个,然后在那个块中构造它们。
当您shared_ptr<T>
从 a创建 a 时T*
,您将分别创建引用计数器/销毁器,并注意拥有的资源是T*
.
的目标shared_from_this
是我们可以从 a 中提取shared_ptr<T>
aT*
基本(假设它存在)。
如果所有共享指针都是通过 创建的make_shared
,这将很容易(除非您希望在失败时定义行为),因为布局很容易。
但是,并非所有共享指针都是以这种方式创建的。有时您可以创建指向不是由任何std
库函数创建的对象的共享指针,因此T*
与共享指针引用计数和销毁数据无关。
由于a或它所指向的内容(通常)没有空间来查找此类构造,因此我们必须将其存储在外部,这意味着全局状态和线程安全开销以及其他痛苦。T*
对于不需要的人来说,这将是一个负担shared_from_this
,并且与确实需要它的人(互斥锁、查找等)的当前状态相比,性能会受到影响。
当前设计将 a 存储weak_ptr<T>
在enable_shared_from_this<T>
. 每当调用或调用 ctor时都会对其weak_ptr
进行初始化。现在我们可以创建一个from ,因为我们通过继承 from 在类中为它“腾出了空间” 。make_shared
shared_ptr<T>
shared_ptr<T>
T*
enable_shared_from_this<T>
这又是极低的成本,并且可以很好地处理简单的情况。我们最终的开销weak_ptr<T>
比T
.
当您有两个不同shared_from_this
的 时,它们的weak_ptr<A>
和weak_ptr<B>
成员是不相关的,因此您想要存储生成的智能指针的位置是不明确的(可能两者都有?)。这种歧义会导致您看到的错误,因为它假设weak_ptr<?>
一个唯一成员中只有一个成员,shared_from_this<?>
而实际上有两个。
链接解决方案提供了一种扩展此功能的巧妙方法。它写道enable_shared_from_this_virtual<T>
。
这里不是存储 a weak_ptr<T>
,而是存储 a ,weak_ptr<Q>
其中Q
是 的虚拟基类enable_shared_from_this_virtual<T>
,并且在虚拟基类中这样做是唯一的。然后,它以非虚拟方式覆盖和类似方法,以提供与使用“成员指针或子类型构造函数”shared_from_this
相同的接口,在该接口中,您以类型安全的方式将引用计数/销毁器组件与拥有的资源组件分开。shared_from_this<T>
shared_ptr
这里的开销比基本的要大shared_from_this
:它具有虚拟继承并强制使用虚拟析构函数,这意味着对象存储了指向虚拟函数表的指针,并且访问shared_from_this
速度较慢,因为它需要虚拟函数表调度。
优点是它“正常工作”。现在在层次结构中有一个唯一shared_from_this<?>
的,您仍然可以获得类型安全的共享指针,指向T
继承自shared_from_this<T>
.
是的,它可以使用类型的全局哈希表
unordered_map< T*, weak_ptr<T> >
从 执行共享指针的查找this
。
#include <memory>
#include <iostream>
#include <unordered_map>
#include <cassert>
using namespace std;
template<class T>
struct MySharedFromThis {
static unordered_map<T*, weak_ptr<T> > map;
static std::shared_ptr<T> Find(T* p) {
auto iter = map.find(p);
if(iter == map.end())
return nullptr;
auto shared = iter->second.lock();
if(shared == nullptr)
throw bad_weak_ptr();
return shared;
}
};
template<class T>
unordered_map<T*, weak_ptr<T> > MySharedFromThis<T>::map;
template<class T>
struct MyDeleter {
void operator()(T * p) {
std::cout << "deleter called" << std::endl;
auto& map = MySharedFromThis<T>::map;
auto iter = map.find(p);
assert(iter != map.end());
map.erase(iter);
delete p;
}
};
template<class T>
shared_ptr<T> MyMakeShared() {
auto p = shared_ptr<T>(new T, MyDeleter<T>());
MySharedFromThis<T>::map[p.get()] = p;
return p;
}
struct Test {
shared_ptr<Test> GetShared() { return MySharedFromThis<Test>::Find(this); }
};
int main() {
auto p = MyMakeShared<Test>();
assert(p);
assert(p->GetShared() == p);
}
但是,无论何时从 T* 构造 shared_ptr 并且在调用删除器之前,都必须更新映射,这会花费时间。此外,为了线程安全,互斥锁必须保护对映射的访问,在线程之间序列化相同类型的分配。所以这个实现不会像enable_shared_from_this
.
更新:
使用 make_shared 使用的相同指针技巧对此进行改进,这是一个应该与 shared_from_this 一样快的实现。
template<class T>
struct Holder {
weak_ptr<T> weak;
T value;
};
template<class T>
Holder<T>* GetHolder(T* p) {
// Scary!
return reinterpret_cast< Holder<T>* >(reinterpret_cast<char*>(p) - sizeof(weak_ptr<T>));
}
template<class T>
struct MyDeleter
{
void operator()(T * p)
{
delete GetHolder(p);
}
};
template<class T>
shared_ptr<T> MyMakeShared() {
auto holder = new Holder<T>;
auto p = shared_ptr<T>(&(holder->value), MyDeleter<T>());
holder->weak = p;
return p;
}
template<class T>
shared_ptr<T> MySharedFromThis(T* self) {
return GetHolder(self)->weak.lock();
}