最近,我浏览了Scott的有效的现代C++,发现关于shared_ptr的讨论非常有趣。我还看了路易·布兰迪( Louis )的奇怪的是,Facebook上反复出现的C++错误演讲,其中也有一些关于shared_ptr工作方式的细节,我认为实现自己的内容会很有趣,看看我是否真正理解了它。
shared_ptr和make_shared:https://godbolt.org/z/8Yec9K的实现
#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的实现进行比较时,我有几个问题:
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对象。不过,我还是想不出怎么用简单的方法来做。有什么简单的方法可以用我现在所拥有的来实现吗?我确信在我的代码中还有很多其他的错误和错误,我会非常感谢所有的反馈。耽误您时间,实在对不起!
发布于 2021-01-31 01:31:32
std::weak_ptr-like类似物。可能是以后实施的一个新挑战;)SharedPtr很好。SharedPtr(nullptr_t, DeleteFunctionType)没有存储删除函数,但后来我意识到没有reset-like功能。SharedPtr的许多成员函数可以标记为noexcept。除此之外,我找不到任何明显需要改进的地方。9~10成熟!
U*不能转换为T*的情况下,从重载解析中删除构造函数。(通常,这允许提供更好的错误消息,并且可能会使另一个重载更适合在存在相同fit重载的情况下)。template<typename T> class SharedPtr<T[]> { ... };,并使用相应的成员函数数组版本。std::shared_ptr通过在控制块上使用类型擦除来避免这种情况。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_;};注意:可以通过使用模板元编程(例如取消对虚拟函数调用的需要)来进一步优化这一点,但是应该简单地介绍这个概念。发布于 2021-01-31 18:37:42
共享指针的主要目的是防止指针的泄漏。因此,一旦将指针传递给共享指针对象,它就会对其负责,并且必须确保它被删除。
在构造函数中,这意味着必须防止构造函数发生故障。注意:如果构造函数无法完成,那么析构函数将不会运行。
在这里,您调用new,这会导致抛出异常,从而导致构造函数未完成和析构函数未被调用。因此,如果指针val失败,此构造函数将泄漏指针new。
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 (并不经常使用)。
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可能不是个好主意。我会迫使应用程序在这一点上退出!
~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。
void swap(SharedPtr& rhs) noexcept {
// ^^^^^^^ Add this.
using std::swap;
swap(val_, rhs.val_);
swap(ctrlBlock_, rhs.ctrlBlock_);
}使用交换区还可以实现移动语义:
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操作符应该是显式的。
explicit operator bool() const {
^^^^^^^^
return val_ != nullptr;
}目前,对bool的比较将强制转换,然后进行比较。
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?
}https://codereview.stackexchange.com/questions/255424
复制相似问题