shared_from_this可以在没有enable_shared_from_this的情况下实现吗?

时间:2015-01-20 18:32:15

标签: c++ c++11 shared-ptr

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

boost shared_from_this and multiple inheritance

可以在不需要enable_shared_from_this的情况下实现shared_from_this吗?如果是这样,是否可以快速制作?

2 个答案:

答案 0 :(得分:7)

shared_ptr是3件事。它是一个参考计数器,一个驱逐舰和一个拥有的资源。

当你make_shared时,它会同时分配所有3个,然后在那个块中构造它们。

当您从shared_ptr<T>创建T*时,您需要单独创建引用计数器/驱逐舰,并注意所拥有的资源是T*

shared_from_this的目标是我们可以从shared_ptr<T>基本上提取T*(假设它存在)。

如果所有共享指针都是通过make_shared创建的,那么这很容易(除非您希望在失败时定义行为),因为布局很容易。

但是,并非所有共享指针都是以这种方式创建的。有时您可以创建一个指向未由任何std库函数创建的对象的共享指针,因此T*与共享指针引用计数和销毁数据无关。

由于T*中没有 room 或者它(通常)指向这样的构造,我们必须将其存储在外部,这意味着全局状态和线程安全开销和其他痛苦。对于不需要shared_from_this的人来说,这将是一种负担,与需要它的人(互斥体,查找等)的当前状态相比,性能会受到影响。

当前设计在weak_ptr<T>中存储enable_shared_from_this<T>。每当调用weak_ptrmake_shared ctor时,都会初始化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<?>成员,实际上有两个。

The linked solution提供了一种灵活的方法来扩展它。它写enable_shared_from_this_virtual<T>

这里不存储weak_ptr<T>,而是存储weak_ptr<Q>,其中Qenable_shared_from_this_virtual<T>的虚拟基类,并且在虚拟基类中唯一存储。然后它非虚拟地覆盖shared_from_this和类似的方法,以提供与shared_from_this<T>使用“成员指针或子类型shared_ptr构造函数”相同的接口,在此处拆分引用计数/驱逐舰来自拥有的资源组件的组件,以类型安全的方式。

此处的开销大于基本shared_from_this:它具有虚拟继承并强制使用虚拟析构函数,这意味着该对象存储指向虚函数表的指针,并且对shared_from_this的访问速度较慢因为它需要虚拟功能表调度。

优点是它“正常”。 heirarchy现在有一个唯一的shared_from_this<?>,你仍然可以获得继承自T的类shared_from_this<T>的类型安全共享指针。

答案 1 :(得分: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);
}

Live Demo

但是,只要从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();
}

Live Demo