2

使用时似乎有一些边缘情况enabled_shared_from_this。例如:

提升 shared_from_this 和多重继承

shared_from_this不使用就可以实现吗enable_shared_from_this如果是这样,它可以做得那么快吗?

4

2 回答 2

7

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_sharedshared_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>.

于 2015-01-20T18:58:07.597 回答
0

是的,它可以使用类型的全局哈希表

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();
}

现场演示

于 2015-01-20T18:32:48.710 回答