首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >shared_ptr和make_shared实现(用于学习)

shared_ptr和make_shared实现(用于学习)
EN

Code Review用户
提问于 2021-01-30 21:05:49
回答 2查看 1.5K关注 0票数 6

最近,我浏览了Scott的有效的现代C++,发现关于shared_ptr的讨论非常有趣。我还看了路易·布兰迪( Louis )的奇怪的是,Facebook上反复出现的C++错误演讲,其中也有一些关于shared_ptr工作方式的细节,我认为实现自己的内容会很有趣,看看我是否真正理解了它。

这里是shared_ptrmake_sharedhttps://godbolt.org/z/8Yec9K

的实现

代码语言:javascript
复制
#include <iostream>
#include <functional>
#include <vector>

template <typename T>
class SharedPtr {
public:
    using DeleteFunctionType = std::function<void(T*)>;

    explicit SharedPtr(): 
        val_(nullptr), 
        ctrlBlock_(nullptr) 
    {
    }

    explicit SharedPtr(std::nullptr_t,
                       DeleteFunctionType = [](T* val) {
                           delete val;
                       }): 
        val_(nullptr),
        ctrlBlock_(nullptr) 
    {
    }

    explicit SharedPtr(T* val, 
                       DeleteFunctionType deleteFunction = [](T* val) {
                           delete val;
                       }):
        val_(val),
        ctrlBlock_(new ControlBlock(1, std::move(deleteFunction))) 
    {
    }

    ~SharedPtr() {
        if (val_ == nullptr) {
            return;
        }
        if (--ctrlBlock_->refCount <= 0) {
            ctrlBlock_->deleteFunction(val_);
            delete ctrlBlock_;

            val_ = nullptr;
            ctrlBlock_ = nullptr;
        }
    }

    SharedPtr(const SharedPtr& rhs):
        val_(rhs.val_), 
        ctrlBlock_(rhs.ctrlBlock_) 
    {
        if (ctrlBlock_ != nullptr) {
            ++ctrlBlock_->refCount;
        }
    }

    SharedPtr& operator=(SharedPtr rhs) {
        swap(rhs);
        return *this;
    }

    void swap(SharedPtr& rhs) {
        using std::swap;
        swap(val_, rhs.val_);
        swap(ctrlBlock_, rhs.ctrlBlock_);
    }

    bool operator==(const SharedPtr& rhs) const {
        return val_ == rhs.val_ && ctrlBlock_ == rhs.ctrlBlock_;
    }

    T* get() const {
        return val_;
    }

    T& operator*() const {
        return *val_;
    }

    T* operator->() const {
        return val_;
    }

    friend void swap(SharedPtr& lhs, SharedPtr& rhs) {
        lhs.swap(rhs);
    }

    operator bool() const {
        return val_ != nullptr;
    }

private: 
    struct ControlBlock {
        ControlBlock(int cnt, DeleteFunctionType fnc): 
            refCount(cnt),
            deleteFunction(fnc) 
        {
        }

        int refCount;
        DeleteFunctionType deleteFunction;
    };

    T* val_;
    ControlBlock* ctrlBlock_;
};


template <typename T, typename... Args>
SharedPtr<T> MakeShared(Args&&... args) {
    return SharedPtr<T>(new T(std::forward<Args>(args)...));
}





struct Foo {
    Foo(int a, int b) : a_(a), b_(b) {}
    int a_;
    int b_;
    void sayHello() {
        std::cout << "Hello from " << *this << "\n";
    }
    friend std::ostream& operator<<(std::ostream& os, const Foo& rhs) {
        os << "Foo(" << rhs.a_ << ", " << rhs.b_ << ")";
        return os;
    }
};

int main() {
    {
        // Basic usage
        SharedPtr<Foo> c; // Default constructor
        SharedPtr<Foo> a(new Foo(1,2)); // Constructor with value
        auto b = a; // Copy constructor
        c = b; // Assignment operator
    }

    {
        // using custom delete
        constexpr int arrSize = 10;
        SharedPtr<int> a(new int[arrSize], [](auto p) {
            delete[] p;
        }); // custom deleter
        auto b = a; // copy constructor -- make sure the custom deleter is propogated
        SharedPtr<int> c;
        c = a; // copy assignment
    }

    {
        // nullptr
        SharedPtr<Foo> a(nullptr);
        auto b = a;
        SharedPtr<Foo> c;
        c = a;
    }

    {
        // Make shared -- basic usage
        SharedPtr<Foo> c; // Default constructor

        auto a = MakeShared<Foo>(2, 3);
        auto b = a; // Copy constructor
        c = b; // Assignment operator
        std::cout << "*c = " << *c << "\n";
        c->sayHello();
    }

    {
        std::vector<SharedPtr<std::vector<int>>> v;
        v.emplace_back();
        v.emplace_back();
        v.emplace_back();
        v.emplace_back();
        v.emplace_back();
        std::cout << v.size() << "\n";
    }
}

谨请提出任何和所有评论意见。特别是,在将我的实现与STL的实现进行比较时,我有几个问题:

  • 我注意到在我查看的STL实现中,类和构造函数是在不同类型上模板化的(即构造函数是像:template <typename T> class shared_ptr { public: template <typename U> explicit shared_ptr(U* val); };那样实现的),我想知道为什么类和构造函数都需要模板化?
  • 下面编译:std::shared_ptr<int[]> a(new int[10]);,但是类似的想法并不是用我的实现来编译的。我怎么才能解决这个问题?
  • 使用std::function来存储自定义删除器有缺点吗?我注意到我看过的STL实现没有做到这一点,但是std::function提供的类型擦除似乎非常适合自定义删除器的工作方式。
  • 我知道std::make_shared应该只使用一个分配来分配控制块和T对象。不过,我还是想不出怎么用简单的方法来做。有什么简单的方法可以用我现在所拥有的来实现吗?

我确信在我的代码中还有很多其他的错误和错误,我会非常感谢所有的反馈。耽误您时间,实在对不起!

EN

回答 2

Code Review用户

回答已采纳

发布于 2021-01-31 01:31:32

通用材料

  • 作为-is的实现不试图以任何方式实现线程安全。我想这是有意的,但如果沟通不好,可能会引起问题和/或混乱。
  • 没有std::weak_ptr-like类似物。可能是以后实施的一个新挑战;)
  • 移动构造函数(和赋值操作符)对SharedPtr很好。
  • 我首先感到困惑的是,为什么SharedPtr(nullptr_t, DeleteFunctionType)没有存储删除函数,但后来我意识到没有reset-like功能。
  • SharedPtr的许多成员函数可以标记为noexcept

除此之外,我找不到任何明显需要改进的地方。9~10成熟!

Q & A

  1. 首先,标准规定了这一点。而且标准要求它,因为它允许一些元编程技巧,在这种情况下,在U*不能转换为T*的情况下,从重载解析中删除构造函数。(通常,这允许提供更好的错误消息,并且可能会使另一个重载更适合在存在相同fit重载的情况下)。
  2. 为数组类型添加一个专门化:template<typename T> class SharedPtr<T[]> { ... };,并使用相应的成员函数数组版本。
  3. 是的,它可能导致第三个分配(例如,如果删除者是一个具有非空捕获子句的lambda )。std::shared_ptr通过在控制块上使用类型擦除来避免这种情况。
  4. 嗯,这本身并不容易。它将需要一些设计更改,主要是在控制块上,并让它实际控制T的生存期(目前,它只控制删除器,而不是T对象本身)。基本上,定义一个ControlBlockBase<T>类,它提供控制块接口。然后创建一个派生的ControlBlockImmediate<T>,它有一个附加的T成员和必要的成员函数。(这也可用于修复std::function第三种可能的分配)。示例: template class ControlBlockBase { public: virtual ()= default;virtual * get() const = 0;void addReference() { ++refCount_;} void (){ -- refCount_;} size_t countReferences() {返回refCount_;} private: size_t refCount_= 0u;} };template class ImmediateControlBlock : public ControlBlockBase { public: template ImmediateControlBlock(Args&.Immediate_(std::forward( args) .){ addReference();} T* get()覆盖{返回&immediate_;}&immediate_;} immediate_;};template class ControlBlock : public ControlBlockBase { public: ControlBlock(T* ptr,Deleter删除):ptr_{ptr},deleter_{删除}{ if (ptr_) addReference();} ~ControlBlock() { assert(countReferences() == 0u);if(ptr_) deleter_(ptr_);} T* get() const覆盖{返回ptr_;} private: T* ptr_;Deleter deleter_;};注意:可以通过使用模板元编程(例如取消对虚拟函数调用的需要)来进一步优化这一点,但是应该简单地介绍这个概念。
票数 6
EN

Code Review用户

发布于 2021-01-31 18:37:42

代码评审

共享指针的主要目的是防止指针的泄漏。因此,一旦将指针传递给共享指针对象,它就会对其负责,并且必须确保它被删除。

在构造函数中,这意味着必须防止构造函数发生故障。注意:如果构造函数无法完成,那么析构函数将不会运行。

在这里,您调用new,这会导致抛出异常,从而导致构造函数未完成和析构函数未被调用。因此,如果指针val失败,此构造函数将泄漏指针new

代码语言:javascript
复制
explicit SharedPtr(T* val, 
                   DeleteFunctionType deleteFunction = [](T* val) {
                       delete val;
                   }):
    val_(val),

    // If this new throws
    // you leak the val pointer.
    ctrlBlock_(new ControlBlock(1, std::move(deleteFunction))) 
{
}

有几个解决方案。您可以使用不抛出的new版本(然后检查ctrlBlock_是否为空)。或者,您可以捕获异常,销毁指针,然后重新抛出异常。我喜欢第二个版本(这也使您有机会在整个构造函数周围使用try/catch (并不经常使用)。

代码语言:javascript
复制
explicit SharedPtr(T* val, 
                   DeleteFunctionType deleteFunction = [](T* val) {
                       delete val;
                   })
try
    : val_(val)
    , ctrlBlock_(new ControlBlock(1, std::move(deleteFunction))) 
{}
catch(...)
{
     deleteFunction(val_);
     throw;
}

如果refCount降到零以下,那么您就有了一个严重的问题。打电话给deleteFunction可能不是个好主意。我会迫使应用程序在这一点上退出!

代码语言:javascript
复制
~SharedPtr() {
    if (val_ == nullptr) {
        return;
    }
    // If the count drops below zero
    // Then you have a serious problem.
    // I would force the application to quit as you have probably already
    // called delete on the object if your code is working
    // or some other object is scribbled over the memory in this object
    // and caused a lot of other issues.
    if (--ctrlBlock_->refCount <= 0) {
        ctrlBlock_->deleteFunction(val_);
        delete ctrlBlock_;

        // Not a fan of setting these to null in the first place.
        // 1: Wate of time.
        // 2: Prevents detection of errors in debug version.
        //
        // BUT why are you doing this only if you delete the
        // object and the control block. If you are going to do
        // it then you should always do it.
        val_ = nullptr;
        ctrlBlock_ = nullptr;
    }
}

您的交换应该标记为noexcept

代码语言:javascript
复制
void swap(SharedPtr& rhs) noexcept {
                    //    ^^^^^^^     Add this.
    using std::swap;
    swap(val_, rhs.val_);
    swap(ctrlBlock_, rhs.ctrlBlock_);
}

使用交换区还可以实现移动语义:

代码语言:javascript
复制
 SharedPtr(SharedPtrSharedPtr&& move) noexcept
     : val_(nullptr)
     , ctrlBlock(nullptr)
 {
     swap(move);
     move = nullptr; // Force the destination to release.
                     // If this was the last pointer this
                     // will force deallocation of the pointed
                     // at object.

                     // BUT do this after the move to enforce the
                     // strong exception gurantee.
 }
 SharedPtr& operator=(SharedPtr&& move) noexcept
 {
     swap(move);
     move = nullptr;  // reset the move to potentially force a delete.
     return *this;
 }

bool操作符应该是显式的。

代码语言:javascript
复制
explicit operator bool() const {
^^^^^^^^
    return val_ != nullptr;
}

目前,对bool的比较将强制转换,然后进行比较。

代码语言:javascript
复制
SharedPtr<int>   x(new int{5});
if (x == false) {
    // Do you want the above to compile?
}

// Or even worse
int val = 1;
if (x == val) {
    // Do you want the above to compile?
    // What does a comparison with int mean?
}
票数 3
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/255424

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档