欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 文旅 > 手游 > shared_ptr

shared_ptr

2025/9/15 9:04:55 来源:https://blog.csdn.net/huangyifei_1111/article/details/144176049  浏览:    关键词:shared_ptr

Shared_Ptr智能指针使用

shared_ptr

通常用auto定义一个对象来保存make_shared的结果

当进行拷贝或赋值操作时,每个shared_ptr都会记录有多少个其它shared_ptr指向相同的对象

可以认为每个shared_ptr都有一个关联的计数器,通常称其为引用计数(reference count)。无论何时拷贝一个shared_ptr,计数器都会递增。例如,当用一个shared_ptr初始化另一个shared_ptr,或将它作为参数传递给一个函数以及作为函数的返回值时,它所关联的计数器就会递增。==当给shared_ptr赋予一个新值或是shared_ptr被销毁(例如一个局部的shared_ptr离开其作用域)时,计数器就会递减。==一旦一个shared_ptr的计数器变为0,它就会自动释放自己所管理的对象。

当指向一个对象的最后一个shared_ptr被销毁时,shared_ptr类会自动销毁此对象。它是通过另一个特殊的成员函数析构函数(destructor)来完成销毁工作的。类似于构造函数,每个类都有一个析构函数。就像构造函数控制初始化一样,析构函数控制此类型的对象销毁时做什么操作。shared_ptr的析构函数会递减它所指向的对象的引用计数。如果引用计数变为0,shared_ptr的析构函数就会销毁对象,并释放它占用的内存。

如果将shared_ptr存放于一个容器中,而后不再需要全部元素,而只使用其中一部分,要记得用erase删除不再需要的那些元素。

1.初始化

  1. 优先使用make_shared来构造智能指针,因为他更高效。

  2. 不能将一个原始指针赋值给智能指针。

  3. 对于一个未初始化的智能指针,可以用reset方法来初始化。

    初始化方法如下:

    // make_shared
    std::shared_ptr<int> pnew = std::make_shared<int>();
    // 直接赋值构造初始化
    std::shared_ptr<int> p(new int(1));
    // 拷贝构造
    std::shared_ptr<int> p2 = p;
    // 使用reset 初始化 reset 会使引用计数减1
    std::shared_ptr<int> ptr;
    ptr.reset(new int(1));
    

2.指定删除器

  • a)普通指定删除实现
void DeletePtr(int* p)
{delete p;
}
std::shared_ptr<int> ptr1(new int, DeletePtr); // 当引用计数为0时,自动调用函数删除

b)使用shared_ptr 管理动态数组

构造时直接定义删除函数:

std::shared_ptr<int> p(new int[10],[](int* p){delete []p;}); // 管理动态数组

封装一个make_shared_arry方法实现:

// 通过shared_ptr创建数组
template<typename T> 
shared_ptr<T> make_shared_array(size_t size)
{return shared_ptr<T>(new T[size], default_delete<T[]>());
}

3.使用注意事项

(1)、不要把一个原生指针给多个shared_ptr管理;

​ 多个shared_ptr 管理一个裸指针会double free。

(2)、不要把this指针给shared_ptr;(double free)

(3)、不要在函数实参里创建shared_ptr;

function (shared_ptr<int>(new int),g());

​ 在C++中函数参数计算顺序在不同编译器下调用顺序可能不同,有的从左到有,有从右到左,可能先调用new int 然后调用g(),恰好g(),发生异常,而shared_ptr 还没有创建,则int 内存泄露了,正确的做法:

// 正确做法
shared_ptr<int> p(new int);
fuction(p,g());

(4)、通过shared_from_this()返回this指针,不要将this指针作为shared_ptr返回来。

struct A
{shared_ptr<A> GetSelf(){return shared_ptr<S>(this); // 不要做么做}
}
int main()
{// double freeshared_ptr<A> sp1(new A);shared_ptr<A> sp2 = sp1->GetSelf();return 0;
}

正确做法:

class A:public std::enable_shared_from_this<A>
{std::shared_ptr<A> GetSelf(){return shared_from_this();}
}
std::shared_ptr<A> sp1(new A);
std::shared_ptr<A> sp2 = sp1->GetSelf(); // OK

(4)、不要不加思考地把指针替换为shared_ptr来防止内存泄漏,shared_ptr并不是万能的,而且使用它们的话也是需要一定的开销的;

**(5)、环状的链式结构shared_ptr将会导致内存泄漏(可以结合weak_ptr来解决);**循环引用

(6)、共享拥有权的对象一般比限定作用域的对象生存更久,从而将导致更高的平均资源使用时间;

(7)、在多线程环境中使用共享指针的代价非常大,这是因为你需要避免关于引用计数的数据竞争;

(8)、共享对象的析构器不会在预期的时间执行;

(9)、不使用相同的内置指针值初始化(或reset)多个智能指针;

(10)、不delete get()返回的指针;

(11)、不使用get()初始化或reset另一个智能指针;

(12)、如果使用get()返回的指针,记住当最后一个对应的智能指针销毁后,你的指针就变为无效了;

(13)、如果你使用智能指针管理的资源不是new分配的内存,记住传递给它一个删除器。

4. effective 讲解

class Widget19 {};
void makeLogEntry(Widget19*) {}
auto loggingDel = [](Widget19* pw) { makeLogEntry(pw); delete pw; }; // custom deleter,自定义析构器int test_item_19()
{std::unique_ptr<Widget19, decltype(loggingDel)> upw(new Widget19, loggingDel); // 析构器型别是智能指针型别的一部分std::shared_ptr<Widget19> spw(new Widget19, loggingDel); // 析构器型别不是智能指针型别的一部分auto pw = new Widget19; // pw是个裸指针//std::shared_ptr<Widget19> spw1(pw, loggingDel); // 为*pw创建一个控制块//std::shared_ptr<Widget19> spw2(pw, loggingDel); // 为*pw创建了第二个控制块// 以上两行语句会导致*pw被析构两次,第二次析构将会引发未定义行为,不推荐上面的用法std::shared_ptr<Widget19> spw1(new Widget19, loggingDel); // 直接传递new表达式std::shared_ptr<Widget19> spw2(spw1); // spw2使用的是和spw1同一个控制块return 0;
}

std::shared_ptr这种智能指针访问的对象采用共享所有权来管理其生存期。没有哪个特定的std::shared_ptr拥有该对象。取而代之的是,所有指涉到它的std::shared_ptr共同协作,确保在不再需要该对象的时刻将其析构。当最后一个指涉到某对象的std::shared_ptr不再指涉到它时(例如,由于该std::shared_ptr被析构,或使其指涉到另一个不同的对象),该std::shared_ptr会析构其指涉到的对象。

std::shared_ptr可以通过访问某资源的引用计数来确定是否自己是最后一个指涉到该资源的。引用计数是个与资源关联的值,用来记录跟踪指涉到该资源的std::shared_ptr数量。 std::shared_ptr的构造函数会使该计数递增(通常如此),而其析构函数会使该计数递减,==而拷贝赋值运算符同时执行两种操作(如果sp1和sp2是指涉到不同对象的std::shared_ptr,则赋值运算”sp1=sp2”将修改sp1,使其指涉到sp2所指涉到的对象。该赋值的净效应是:最初sp1所指涉到的对象的引用计数递减,同时sp2所指涉到的对象的引用计数递增)。==如果某个std::shared_ptr发现,在实施过一次递减后引用计数变成了零,即不再有std::shared_ptr指涉到该资源,则std::shared_ptr会析构之。

引用计数的存在会带来一些性能影响:

(1).std::shared_ptr的尺寸是裸指针的两倍。因为它们内部既包含一个指涉到该资源的裸指针,也包含一个指涉到该资源的引用计数的裸指针。

(2).引用计数的内存必须动态分配。std::shared_ptr若是由std::make_ptr创建,可以避免动态分配的成本。然而仍有一些场景下,不可以使用std::make_ptr(自定义删除器)。但无论是不是使用std::make_ptr,引用计数都会作为动态分配的数据来存储。

(3).引用计数的递增和递减必须是原子操作。因为在不同的线程中可能存在并发的读写器。

从一个已有std::shared_ptr移动构造一个新的std::shared_ptr会将源std::shared_ptr置空,这意味着一旦新的std::shared_ptr产生后,原有的std::shared_ptr将不再指涉到其资源,结果是不需要进行任何引用计数操作。因此,移动std::shared_ptr比拷贝它们要快:拷贝要求递增引用计数,而移动则不需要。这一点对于构造和赋值操作同样成立,所以,移动构造函数比拷贝构造函数快,移动赋值比拷贝赋值快。

与std::unique_ptr类似,std::shared_ptr也使用delete运算符作为其默认资源析构机制,但它同样支持自定义析构器。然而这种支持的设计却与std::unique_ptr有所不同。

对于std::unique_ptr而言,析构器的型别是智能指针型别的一部分。但对于std::shared_ptr而言,却并非如此。

与std::unique_ptr的另一点不同,是自定义析构器不会改变std::shared_ptr的尺寸。无论析构器是怎样的型别,std::shared_ptr对象的尺寸都相当于裸指针的两倍。

每一个由std::shared_ptr管理的对象都有一个控制块。除了包含引用计数之外,如果该自定义析构器被指定的话,该控制块还包含自定义析构器的一个拷贝。如果指定了一个自定义内存分配器,控制块也会包含一份它的拷贝。控制块还有可能包含其它附加数据,如被称为弱计数的次级引用计数。

一个对象的控制块由创建首个指涉到该对象的std::shared_ptr的函数来确定。控制块的创建遵循以下规则:

(1).std::make_shared总是创建一个控制块。

(2).从具备专属所有权的指针(即std::unique_ptr或std::auto_ptr指针)出发构造一个std::shared_ptr时,会创建一个控制块。专属所有权指针不使用控制块。

(3).当std::shared_ptr构造函数使用裸指针作为实参来调用时,它会创建一个控制块。

尽可能避免将裸指针传递给一个std::shared_ptr的构造函数。常用的替代手法,是使用std::make_shared。如果必须将一个裸指针传递给std::shared_ptr的构造函数,就直接传递new运算符的结果,而非传递一个裸指针变量。

当你希望一个托管到std::shared_ptr的类能够安全地由this指针创建一个std::shared_ptr时,可以使用std::enable_shared_from_this。std::enable_shared_from_this是一个基类模板,其型别形参总是其派生类的类名。std::enable_shared_from_this定义了一个成员函数,它会创建一个std::shared_ptr指涉到当前对象,但同时不会重复创建控制块。这个成员函数的名字是shared_from_this,每当你需要一个和this指针指涉到相同对象的std::shared_ptr时,都可以在成员函数中使用它。

std::shared_ptr不能处理数组。std::shared_ptr的API仅被设计用来处理指涉到单个对象的指针,并没有所谓的std::shared_ptr<T[]>。

要点速记:

(1).std::shared_ptr提供方便的手段,实现了任意资源在共享所有权语义下进行生命周期管理的垃圾回收。

(2).与std::unique_ptr相比,std::shared_ptr的尺寸通常是裸指针尺寸的两倍,它还会带来控制块的开销,并要求原子化的引用计数操作。

(3).默认的资源析构通过delete运算符进行,但同时也支持定制删除器。删除器的型别对std::shared_ptr的型别没有影响。

(4).避免使用裸指针型别的变量来创建std::shared_ptr指针。

热搜词