首页 > 编程语言 >C++11 智能指针 shared_ptr

C++11 智能指针 shared_ptr

时间:2023-01-17 01:33:05浏览次数:48  
标签:11 int sPtr1 C++ 对象 shared ptr 指针

C++11 智能指针 shared_ptr

Written on 2023-01-16

std::shared_ptr<T> 共享智能指针,也被称为计数智能指针。

共享智能指针会记录有多少个共享智能指针指向同一个对象,当这个数为 0 的时候,程序自动的默认释放(析构)这个对象,记录有多少个的这个方法叫做引用计数

共享智能指针可以有多个共享智能指针同时管理同一个对象。

举个栗子

普通指针管理:

#include <iostream>
#include <memory>
using namespace std;

class Person{
public: 
	Person(){
		cout << "Constructor: person's age = " << m_age << endl;
	}
	Person(int age){
		m_age = age;
		cout << "Constructor: person's age = " << m_age << endl;
	}
	~Person(){
		cout << "Destructor: person's age = " << m_age << endl;
	}
private:
	int m_age = 0; 
}; 

int main()
{
    {
        Person *p = new Person(18);
    }

    cout << "Before return" << endl;
    return 0;
}
/** 输出:
Constructor: person's age = 18
Before return

**/

依输出可见,p指向的对象并没有被析构,因为没有析构函数打印的数据,这造成了内存泄漏。

shared_ptr智能指针管理:

int main()
{
    {
		shared_ptr<Person> sPtr1 {new Person(18)};
    }

    cout << "Before return" << endl;
    return 0;
}
/** 输出:
Constructor: person's age = 18
Destructor: person's age = 18
Before return

**/

依输出可见,打印了析构函数的数据,p指向的对象自动的被析构了。

获取引用计数

long use_count() const noexcept;

shared_ptrnullptr 时,返回为 0。

初始化

创建空管理的 shared_ptr

constexpr shared_ptr() noexcept;
constexpr shared_ptr(std::nullptr_t) noexcept;
  • std::nullptr_t:空指针nullptr

创建对非数组对象和数组对象管理的 shared_ptr

template< class Y >
explicit shared_ptr( Y* ptr );
  • Y:动态分配的对象的类型

例:

#include <iostream>
#include <memory>

using namespace std;

int main()
{
    // 创建空管理的shared_ptr
    shared_ptr<int> sPtr1;
    cout << "sPtr1 use count = " << sPtr1.use_count() << endl;
    // 创建空管理的shared_ptr
    shared_ptr<int> sPtr2(nullptr);
    cout << "sPtr2 use count = " << sPtr2.use_count() << endl;
	// 创建对非数组对象管理的shared_ptr
    shared_ptr<int> sPtr3(new int(100));
    cout << "sPtr3 use count = " << sPtr3.use_count() << endl;
    // 创建对数组对象管理的shared_ptr
    shared_ptr<int> sPtr4(new int[10]);
    cout << "sPtr4 use count = " << sPtr4.use_count();
    return 0;
}
/** 输出:
sPtr1 use count = 0
sPtr2 use count = 0
sPtr3 use count = 1
sPtr4 use count = 1
**/

解释:
sPtr1sPtr2都为nullptr,故引用计数都为 0;
sPtr3sPtr4所管理的对象,都只有一个 shared_ptr 管理,故引用计数都为 1。

创建指定删除器的 shared_ptr

template< class Y, class Deleter >
shared_ptr( Y* ptr, Deleter d );

template< class Deleter >
shared_ptr( std::nullptr_t ptr, Deleter d );

template< class Y, class Deleter, class Alloc >
shared_ptr( Y* ptr, Deleter d, Alloc alloc );

template< class Deleter, class Alloc >
shared_ptr( std::nullptr_t ptr, Deleter d, Alloc alloc );
  • Deleter:删除器,在引用计数为 0 时,shared_ptr 会自动的执行删除器
  • Alloc:分配器(Allocator)
  1. 默认删除器:默认对指向的内存地址进行释放,即析构掉这块地址的内容
    1. 非数组类型,以 delete ptr 为默认删除器
    2. 数组类型,以 delete[] ptr 为默认删除器
  2. 指定删除器:能够自行指定引用计数为 0 时,要做什么,比如有些场景不需要对指向的内存进行释放,比如关闭指向文件,关闭指向套接字
    1. 指定删除器的函数结构:
      void deletePtr(T* p)
      {
          ...
      }
      
    2. 也可使用 Lambda表达式

例,对于文件的使用场景,不是直接delete文件,而是关闭文件:

void closeFile(FILE* fp) 
{
    if (fp == nullptr) return;
    fclose(fp);
    cout << "File closed" << endl;
}
int main() 
{
    FILE* fp = fopen("data.txt" ,"w");
    shared_ptr<FILE> sfp{fp,closeFile};
    if (sfp == nullptr)
        cout << "Failed opened" << endl;
    else 
        cout << "File opened" << endl;
}

创建通过现有 shared_ptrshared_ptr

主要是通过复制构造函数和移动构造函数。

shared_ptr( const shared_ptr& r ) noexcept;

template< class Y >
shared_ptr( const shared_ptr<Y>& r ) noexcept;

shared_ptr( shared_ptr&& r ) noexcept;

template< class Y >
shared_ptr( shared_ptr<Y>&& r ) noexcept;	

例:

#include <iostream>
#include <memory>

using namespace std;

int main()
{
    shared_ptr<int> sPtr1(new int(100));
    cout << "sPtr1 use count = " << sPtr1.use_count() << endl << endl;
    // 创建通过现有 shared_ptr 的 shared_ptr
    // 通过拷贝构造函数创建
    shared_ptr<int> sPtr2(sPtr1);
    cout << "sPtr1 use count = " << sPtr1.use_count() << endl;
    cout << "sPtr2 use count = " << sPtr2.use_count() << endl << endl;
    // 通过赋值运算符创建
    shared_ptr<int> sPtr3 = sPtr1;
    cout << "sPtr1 use count = " << sPtr1.use_count() << endl;
    cout << "sPtr2 use count = " << sPtr2.use_count() << endl;
    cout << "sPtr3 use count = " << sPtr3.use_count() << endl << endl;
    // 通过移动构造函数创建
    shared_ptr<int> sPtr4(move(sPtr1));
    cout << "sPtr1 use count = " << sPtr1.use_count() << endl;
    cout << "sPtr2 use count = " << sPtr2.use_count() << endl;
    cout << "sPtr3 use count = " << sPtr3.use_count() << endl;
    cout << "sPtr4 use count = " << sPtr4.use_count() << endl << endl;
    std::shared_ptr<int> sPtr5 = move(sPtr2);
    cout << "sPtr1 use count = " << sPtr1.use_count() << endl;
    cout << "sPtr2 use count = " << sPtr2.use_count() << endl;
    cout << "sPtr3 use count = " << sPtr3.use_count() << endl;
    cout << "sPtr4 use count = " << sPtr4.use_count() << endl;
    cout << "sPtr5 use count = " << sPtr5.use_count();
    return 0;
}
/** 输出:
sPtr1 use count = 1

sPtr1 use count = 2
sPtr2 use count = 2

sPtr1 use count = 3
sPtr2 use count = 3
sPtr3 use count = 3

sPtr1 use count = 0
sPtr2 use count = 3
sPtr3 use count = 3
sPtr4 use count = 3

sPtr1 use count = 0
sPtr2 use count = 0
sPtr3 use count = 3
sPtr4 use count = 3
sPtr5 use count = 3
**/

解释:
sPtr2sPtr3都是通过对sPtr1执行了复制构造函数,因此sPtr1的引用计数一次增加 1;sPtr2sPtr3同理。
通过move(sPtr1),使得sPtr1释放了被管理对象的所有权,此时sPtr1被设置为nullptr,因此sPtr1引用计数为 0;sPtr2同理。

通过 std::make_shared

template< class T, class... Args >
shared_ptr<T> make_shared( Args&&... args );
  • T:非数组,指针指向的数据类型
  • Args&&... argsT 的构造函数参数列表

这种初始化效率更高,在 C++17 之前的编译器更安全

#include <iostream>
#include <memory>

using namespace std;

int main()
{
    shared_ptr<int> sPtr1 = make_shared<int>(100);
    shared_ptr<int> sPtr2(make_shared<int>(200));
	shared_ptr<int> sPtr3{make_shared<int>(300)};
    return 0;
}

通过 std::reset

使用std::reset,使得释放对shared_ptr原管理对象的所有权,转为对新对象管理的所有权。

void reset() noexcept;

template< class Y >
void reset( Y* ptr );

template< class Y, class Deleter >
void reset( Y* ptr, Deleter d );

template< class Y, class Deleter, class Alloc >
void reset( Y* ptr, Deleter d, Alloc alloc );

注意,reset若传入的所指向的对象已被占有,程序会异常。

#include <iostream>
#include <memory>

using namespace std;

int main()
{
    shared_ptr<int> sPtr1(new int(100));
    shared_ptr<int> sPtr2 = sPtr1;
    shared_ptr<int> sPtr3 = sPtr2;
    shared_ptr<int> sPtr4 = sPtr1;
    cout << "sPtr1 use count = " << sPtr1.use_count() << endl;
    cout << "sPtr2 use count = " << sPtr2.use_count() << endl;
    cout << "sPtr3 use count = " << sPtr3.use_count() << endl;
    cout << "sPtr4 use count = " << sPtr4.use_count() << endl << endl;
    
    sPtr4.reset();
    cout << "sPtr1 use count = " << sPtr1.use_count() << endl;
    cout << "sPtr2 use count = " << sPtr2.use_count() << endl;
    cout << "sPtr3 use count = " << sPtr3.use_count() << endl;
    cout << "sPtr4 use count = " << sPtr4.use_count() << endl << endl;
    
    sPtr3.reset(new int(100));
    cout << "sPtr1 use count = " << sPtr1.use_count() << endl;
    cout << "sPtr2 use count = " << sPtr2.use_count() << endl;
    cout << "sPtr3 use count = " << sPtr3.use_count() << endl;
    cout << "sPtr4 use count = " << sPtr4.use_count();
    
    std::shared_ptr<int> sPtr5;
    sPtr5.reset(sPtr1.get()); // 异常
    return 0;
}
/** 输出:
sPtr1 use count = 4
sPtr2 use count = 4
sPtr3 use count = 4
sPtr4 use count = 4

sPtr1 use count = 3
sPtr2 use count = 3
sPtr3 use count = 3
sPtr4 use count = 0

sPtr1 use count = 2
sPtr2 use count = 2
sPtr3 use count = 1
sPtr4 use count = 0
**/

解释:

  1. 通过复制构造函数后,sPtr1-4的引用计数均为 4。
  2. sPtr4使用了reset()后,释放被管理对象的所有权,被管理对象的shared_ptr个数减 1,sPtr4被设置为nullptr,同时,sPtr1-3的引用计数均变为 3。
  3. sPtr3使用了reset(new int(100))后,释放原被管理对象的所有权,被管理对象的shared_ptr个数减 1,sPtr4被设置为新的管理对象int(100),同时,sPtr1-3的引用计数均变为 2。
  4. sPtr5reset传入的所指向的对象已被占有,程序异常,没有正常结束。

reset 释放被管理对象的所有权

用于取消shared_ptr对管理对象的所有权,当这个对象被shared_ptr管理的数量为 0,会执行删除器。

使用shared_ptr.reset(),会使得shared_ptr设置为nullptr

就官方文档:

#include <memory>
#include <iostream>
 
struct Foo {
    Foo(int n = 0) noexcept : bar(n) {
        std::cout << "Foo: constructor, bar = " << bar << '\n';
    }
    ~Foo() {
         std::cout << "Foo: destructor, bar = " << bar << '\n';
    }
    int getBar() const noexcept { return bar; }
private:
    int bar;
};
 
int main()
{
    std::shared_ptr<Foo> sptr = std::make_shared<Foo>(1);
    std::cout << "The first Foo's bar is " << sptr->getBar() << "\n";
 
    // 重置,交与新的 Foo 实例
    // (此调用后将销毁旧实例)
    sptr.reset(new Foo);
    std::cout << "The second Foo's bar is " << sptr->getBar() << "\n";
}
/** 输出:
Foo: constructor, bar = 1
The first Foo's bar is 1
Foo: constructor, bar = 0
Foo: destructor, bar = 1
The second Foo's bar is 0
Foo: destructor, bar = 0
**/

解释:
当调用sptr.reset(new Foo);bar = 1对象没有shared_ptr管理,bar = 1对象被释放(析构);程序运行结束前,析构管理bar = 0对象的shared_ptrbar = 0对象没有shared_ptr管理,bar = 0对象被释放(析构)。

获取原始储存的指针

T* get() const noexcept;

解引用存储的指针 operator* and operator->

可以像普通指针一样,使用shared_ptr对所管理的对象进行访问

int main()
{
    shared_ptr<int> sPtr1 {new int(100)};
    cout << *sPtr1 << endl << endl;
    
    int *i = sPtr1.get();
    cout << *i << endl;
    cout << *sPtr1 << endl << endl;
    
    *i = 200;
    cout << *i << endl;
    cout << *sPtr1 << endl << endl;
    
    *sPtr1 = 300;
    cout << *i << endl;
    cout << *sPtr1;
    return 0;
}
/** 输出:
100

100
100

200
200

300
300
**/

直观展示自动管理内存

#include <iostream>
#include <memory>
using namespace std;

class Person{
public: 
	Person(){
		cout << "Constructor: person's age = " << m_age << endl;
	}
	Person(int age){
		m_age = age;
		cout << "Constructor: person's age = " << m_age << endl;
	}
	~Person(){
		cout << "Destructor: person's age = " << m_age << endl;
	}
private:
	int m_age = 0; 
}; 

int main()
{
    shared_ptr<Person> sPtr1 {make_shared<Person>()};
    shared_ptr<Person> sPtr2 {make_shared<Person>(18)};
    shared_ptr<Person> sPtr3 {make_shared<Person>(22)};
    
    shared_ptr<Person> sPtr4 = sPtr1;
    sPtr1.reset();
    sPtr2.reset();
    
    sPtr3.reset(new Person(19));
    
    cout << "Before return" << endl;
    return 0;
}
/** 输出:
Constructor: person's age = 0
Constructor: person's age = 18
Constructor: person's age = 22
Destructor: person's age = 18
Constructor: person's age = 19
Destructor: person's age = 22
Before return
Destructor: person's age = 0
Destructor: person's age = 19

**/

解释:

  1. 定义了三个shared_ptrsPtr1sPtr2sPtr3,它们管理的对象分别为age = 0age = 18age = 22,打印了三行Person对象的构造函数中的输出
  2. 通过赋值运算符,使得sPtr4同时管理sPtr1管理的对象
  3. 释放sPtr1被管理对象的所有权,此时因为sPtr4还在管理原sPtr1管理的对象age = 0,因此age = 0对象并没有被析构
  4. 释放sPtr2被管理对象的所有权,此时因为age = 18对象没有任何shared_ptr进行管理,age = 18对象被析构,打印了age = 18对象的析构函数中的输出
  5. 释放sPtr3被管理对象的所有权,sPtr3转为管理age = 19的对象;是先构造age = 19对象,后再释放sPtr3被管理对象的所有权,打印了age = 19对象的构造函数中的输出;此时因为sPtr3原管理的age = 22对象没有任何shared_ptr进行管理,age = 22对象被析构,打印了age = 22对象的析构函数中的输出
  6. 在程序返回前Before return,管理age = 0age = 19对象的智能指针sPtr4sPtr3被析构,age = 0age = 19对象没有智能指针管理,age = 0age = 19对象被析构,打印了它们对象的析构函数中的输出。

别名

template< class Y >
shared_ptr( const shared_ptr<Y>& r, element_type* ptr ) noexcept;

别名用于访问类的成员变量。

我们需要访问的是某个实例的成员,因此并不希望在使用这个成员的时候,对应的实例被销毁了。

使用别名的shared_ptr,增加了对这个实例的控制权,但仍然访问的是成员。

struct Person{ int age = 18; };
struct Student{ Person person; };

int main(){
    shared_ptr<Student> studentPtr{make_shared<Student>()};
    cout << "studentPtr use count = " << studentPtr.use_count() << endl;
    shared_ptr<Person> personPtr{studentPtr, &(studentPtr->person)};
    cout << "studentPtr use count = " << studentPtr.use_count() << endl;
    cout << personPtr->age << endl;
    return 0;
}
/** 输出:
studentPtr use count = 1
studentPtr use count = 2
18

**/

shared_ptr 与函数

管理动态数组需要指定删除器

shared_ptr 的默认删除器不支持释放数组对象,需要指定删除器

例,一维数组指定删除器

    shared_ptr<int> ptr(new int[10], [](int* p) {delete[] p; });

同时,也可以使用 std::default_delete<T>() 函数作为删除器,这个函数内部的删除功能是通过delete释放,T 为释放什么类型的内存的类型。

例,一维数组指定删除器

    shared_ptr<int> ptr(new int[10], default_delete<int[]>());

可以自己封装模板函数来使 shared_ptr 支持释放数组对象。

#include <iostream>
#include <memory>

using namespace std;

template <typename T>
shared_ptr<T> arrayShared_ptr(size_t size)
{
    // 返回匿名对象
    return shared_ptr<T>(new T[size], default_delete<T[]>());
}

int main()
{
    shared_ptr<int> sPtr1 = arrayShared_ptr<int>(100);
    shared_ptr<int> sPtr2 = arrayShared_ptr<char>(200);
    return 0;
}

危险行为

仍然可使用delete释放智能指针管理的对象的地址

可以使用delete释放shared_ptr管理的对象的地址,但是其它共享指针仍然可能会访问这块地址,若访问了则会出现程序异常,因此应避免使用手动的delete

int main(){
    shared_ptr<int> sPtr1 {new int(100)};
    shared_ptr<int> sPtr2 = sPtr1;
    delete sPtr1.get();
    cout << *sPtr2 << endl;
}

同时存在裸指针和智能指针

如果一个地址内存,同时有裸指针和shared_ptr指向它,即使当所有shared_ptr都被销毁,裸指针还依然存在的情况下,这个地址内存仍然会被释放,若再用裸指针去访问这个内存地址就是未定义的行为。

int main(){
    int *i1 = new int{100};
    shared_ptr<int> sPtr1{i};
    int *i2 = sPtr1.get();
    delete sPtr1;
    cout << *i1 << endl;
    cout << *i2 << endl;
}

同时,无论是使用shared_ptr,还是其它的智能指针,都应该避免与裸指针混用。

避免同时存在裸指针和智能指针的解决方案:

    int *i = new int{100};
    shared_ptr<int> sPtr1{i};
    i = nullptr;
    delete i;
    cout << *sPtr1 << endl;

使用一个裸指针初始化多个shared_ptr

不能使用一个裸指针初始化多个shared_ptr

    int * i = new int(100);
    shared_ptr<int> sPtr1{i};
    shared_ptr<int> sPtr2{i};// error 编译通过 运行错误

最后

shared_ptr 由于使用引用计数,因此会造成额外的内存和性能开销,因此在性能要求极为苛刻的情况下不适用。

unique_ptr是 0 开销的智能指针,也能够自动管理内存,但不会造成性能损失。

标签:11,int,sPtr1,C++,对象,shared,ptr,指针
From: https://www.cnblogs.com/champrin/p/17056814.html

相关文章