一、智能指针介绍
智能指针的作用是管理一个指针,因为存在以下这种情况:申请的空间在函数结束时忘记释放,造成内存泄漏。使用智能指针可以很大程度上的避免这个问题,因为智能指针是一个类,当超出了类的实例对象的作用域时,会自动调用对象的析构函数,析构函数会自动释放资源。所以智能指针的作用原理就是在函数结束时自动释放内存空间,不需要手动释放内存空间。智能指针主要用于管理在堆上分配的内存,它将普通的指针封装为一个栈对象。当栈对象的生存周期结束后,会在析构函数中释放掉申请的内存,从而防止内存泄漏。
二、详解
1.auto_ptr
ps:C++98的方案,现在通用的是C++11,最新的是C++20,这个auto_ptr指针现在已经被unique_ptr代替,所以可以忽略掉。
2.unique_ptr
unique是独有特有的意思,所以这个unique_ptr指的是独占式指针,保证同一时间内只有一个指针指向一个对象,并且不能拷贝或者复制给其他对象,其拥有的内存仅自己独占
<1>std::unique_ptr的实现如下:
template <class T>
class unique_ptr
{
//省略其他代码...
//拷贝构造函数和赋值运算符被标记为delete
unique_ptr(const unique_ptr &) = delete;
unique_ptr &operator=(const unique_ptr &) = delete;
};
<2>初始化一个std::unique_ptr对象方法:
int main()
{
//方式1
std::unique_ptr<int> object1(new int(123));
//方式2
std::unique_ptr<int> object2;
object2.reset(new int(123));
//方式3
std::unique_ptr<int> object3 = std::make_unique<int>(123);
}
通常用方式3的std::make_unique去创建并初始化一个unique_ptr的对象
<3>std::make_unique的实现如下:
template <typename T, typename... Ts>
std::unique_ptr<T> make_unique(Ts &&...params)
{
return std::unique_ptr<T>(new T(std::forward<Ts>(params)...));
}
<4>std::unique_ptr不仅可以持有一个对象,还可以持有一组对象,实现如下:
#include <memory>
#include <iostream>
int main()
{
//创建10个int类型对象
//方式1
std::unique_ptr<int[]> object1(new int[10]);
//方式2
std::unique_ptr<int[]> object2;
object2.reset(new int[10]);
//方式3
std::unique_ptr<int[]> object3(std::make_unique<int[]>(10));
for (int i = 0; i < 10; ++i)
{
object1[i] = i;
object2[i] = i;
object3[i] = i;
}
std::cout << "begin~" << std::endl;
for (int i = 0; i < 10; ++i)
{
std::cout << object1[i] << " " << object2[i] << " " << object3[i] << std::endl;
}
std::cout << "end~" << std::endl;
return 0;
}
打印如下:
<5>上面说了std::unique_ptr是独占式指针,不能赋值和复制,那怎么将指针A交给另一个指针B呢?答案是:移动构造,即std::move方法。但是指针A一旦把内存交给了指针B,那A将对这一块堆内存没有拥有权,用下面的代码来验证一下:
#include <memory>
#include <iostream>
int main()
{
std::unique_ptr<int> object1(std::make_unique<int>(123));//创建一个unique_ptr对象object1并初始化
//判断object1
if (object1.get() == nullptr) {
std::cout << "object1 is nullptr" << std::endl;
} else {
std::cout << "object1 isn't nullptr" << std::endl;
}
//将object1交给object2,并判断object1和object2
std::unique_ptr<int> object2(std::move(object1));
if (object1.get() == nullptr) {
std::cout << "object1 is nullptr" << std::endl;
} else {
std::cout << "object1 isn't nullptr" << std::endl;
}
if (object2.get() == nullptr) {
std::cout << "object2 is nullptr" << std::endl;
} else {
std::cout << "object2 isn't nullptr" << std::endl;
}
//同上
std::unique_ptr<int> object3;
object3 = std::move(object2);
if (object2.get() == nullptr) {
std::cout << "object2 is nullptr" << std::endl;
} else {
std::cout << "object2 isn't nullptr" << std::endl;
}
if (object3.get() == nullptr) {
std::cout << "object3 is nullptr" << std::endl;
} else {
std::cout << "object3 isn't nullptr" << std::endl;
}
return 0;
}
<6>std::unique_ptr常用API
//释放当前由 unique_ptr(如果有)管理的指针并获得参数 p(参数 p 默认为 NULL)的所有权。如果p是空指针(例如默认初始化的指针),则 unique_ptr 变为空,调用后不管理任何对象。
void reset(pointer p = pointer())
//返回管理的指针并将其替换为空指针, 释放其管理指针的所有权。这个调用并不会销毁托管对象,但是将 unique_ptr对象管理的指针解脱出来。如果要强制销毁所指向的对象,请调用 reset 函数或对其执行赋值操作。
pointer release()
//返回存储的指针,不会使 unique_ptr 释放指针的所有权。因此,该函数返回的值不能于构造新的托管指针,如果为了获得存储的指针并释放其所有权,请调用 release。
element_type* get()
//将unique_ptr对象的内容与对象 x 进行交换,在它们两者之间转移管理指针的所有权而不破坏二者。
void swap (unique_ptr& x)
3.shared_ptr
<1>std::shared_ptr解释
std::shared_ptr这个智能指针和上面刚说的std::unique_ptr属性刚好相反,你看它的名字里有一个shared,它表示共享的,很明显,这个指针持有的资源可以在多个 std::shared_ptr 之间共享,并且这个std::shared_ptr有一个计数功能,每多一个 std::shared_ptr 对资源引用,资源引用计数将增加 1,每一个指向该资源的 std::shared_ptr 对象析构时,资源引用计数减 1,最后一个 std::shared_ptr 对象析构时,发现资源计数为 0,将释放其持有的资源。多个线程之间,增加或者减少资源的引用计数是安全的。但是这并不代表多线程同时操作对象是安全的。这个std::shared_ptr比上面的std::unique_ptr多了一个 use_count()方法,就是引用计数的方法,其他方法用法类似
<2>创建并初始化一个std::shared_ptr对象,和上面一样,仍然有三种方法,看下面👇
int main()
{
//方式1
std::shared_ptr<int> object1(new int(123));
//方式2
std::shared_ptr<int> object2;
object2.reset(new int(123));
//方式3
std::shared_ptr<int> object3 = std::make_shared<int>(123);
}
仍然优先使用std::make_shared去创建一个std::shared_ptr对象
<3>来一块看看它的引用计数功能,但下面代码:
int main()
{
std::shared_ptr<int> object1(std::make_shared<int>());
std::cout << "usecount: " << object1.use_count() << std::endl;
std::shared_ptr<int> object2(std::make_shared<int>());
object2 = object1;
std::cout << "usecount: " << object1.use_count() << std::endl;
std::shared_ptr<int> object3(std::make_shared<int>());
object3 = object2;
std::cout << "usecount: " << object1.use_count() << std::endl;
object2.reset();
std::cout << "usecount: " << object1.use_count() << std::endl;
return 0;
}
打印结果:
<4>std::shared_ptr常用API
//将 shared_ptr 对象的内容与对象 x 进行交换,在它们两者之间转移管理指针的所有权而不破坏或改变二者的引用计数。
void swap (unique_ptr& x)
//没有参数时,先将管理的计数器引用计数减一并将管理的指针和计数器置清零。有参数 p 时,先做面前没有参数的操作,再管理 p 的所有权和设置计数器。
void reset()
void reset (ponit p)
//得到其管理的指针
element_type* get()
//返回与当前智能指针对象在同一指针上共享所有权的 shared_ptr 对象的数量,如果这是一个空的 shared_ptr,则该函数返回 0。如果要用来检查 use_count 是否为 1,可以改用成员函数 unique 会更快。
long int use_count()
//返回当前 shared_ptr 对象是否不和其他智能指针对象共享指针的所有权,如果这是一个空的 shared_ptr,则该函数返回 false。
bool unique()
//重载指针的 * 运算符,返回管理的指针指向的地址的引用。
element_type& operator\*()
//重载指针的 -> 运算符,返回管理的指针,可以访问其成员。
element_type* operator->()
//返回存储的指针是否已经是空指针,返回的结果与 get() != 0 相同。
explicit operator bool()
<5>重点提一嘴,std::enable_shared_from_this 实际开发中,有时候需要在类中返回包裹当前对象(this)的一个 std::shared_ptr 对象给外部使用,C++ 新标准也为我们考虑到了这一点,有如此需求的类只要继承?std::enable_shared_from_this 模板对象即可。
示例:
#include <iostream>
#include <memory>
class A : public std::enable_shared_from_this<A>
{
public:
A()
{
std::cout << "constructor" << std::endl;
}
~A()
{
std::cout << "destructor" << std::endl;
}
std::shared_ptr<A> GetSelf()
{
return shared_from_this();
}
};
int main()
{
std::shared_ptr<A> object1(new A());
std::shared_ptr<A> object2 = object1->GetSelf();
std::cout << "use count: " << object1.use_count() << std::endl;
return 0;
}
?打印如下:
4.weak_ptr
<1>介绍
std::weak_ptr 是一个不控制资源生命周期的智能指针,是对对象的一种弱引用,只是提供了对其管理的资源的一个访问手段,引入它的目的为协助 std::shared_ptr 工作。
<2>std::weak_ptr的作用
std::weak_ptr 可以从一个 std::shared_ptr 或另一个 std::weak_ptr 对象构造,std::shared_ptr 可以直接赋值给 std::weak_ptr ,也可以通过 std::weak_ptr 的 lock() 函数来获得 std::shared_ptr。它的构造和析构不会引起引用计数的增加或减少。std::weak_ptr 可用来解决 std::shared_ptr 相互引用时的死锁问题(即两个std::shared_ptr 相互引用,那么这两个指针的引用计数永远不可能下降为 0, 资源永远不会释放
示例代码如下:
#include <iostream>
#include <memory>
int main()
{
//创建一个std::shared_ptr对象
std::shared_ptr<int> object1(new int(123));
std::cout << "usecount: " << object1.use_count() << std::endl;
//通过构造函数得到一个std::weak_ptr对象
std::weak_ptr<int> object2(object1);
std::cout << "usecount: " << object1.use_count() << std::endl;
//通过赋值运算符得到一个std::weak_ptr对象
std::weak_ptr<int> object3 = object1;
std::cout << "usecount: " << object1.use_count() << std::endl;
//通过一个std::weak_ptr对象得到另外一个std::weak_ptr对象
std::weak_ptr<int> object4 = object2;
std::cout << "usecount: " << object1.use_count() << std::endl;
//通过一个std::shared_ptr对象得到另外一个std::weak_ptr对象
std::shared_ptr<int> object5 = object1;
std::cout << "usecount: " << object1.use_count() << std::endl;
return 0;
}
最后打印结果如下:
<3>既然std::weak_ptr不管理对象的声明的周期,那如何确认对象是否还存在呢?
std::weak_ptr 提供了一个 expired() 方法来做这一项检测,返回值如果为?true,说明其引用的资源已经不存在了;返回值为 false,说明该资源仍然存在,这个时候可以使用 std::weak_ptr 的 lock() 方法得到一个 std::shared_ptr 对象然后继续操作资源,这里一定要注意得到的是std::shared_ptr,然后用的是std::shared_ptr对象操作资源,不可以直接操作std::weak_ptr对象,因为std::weak_ptr 类没有重写 operator-> 和 operator* 方法,因此不能像 std::shared_ptr 或 std::unique_ptr 一样直接操作对象,同时 std::weak_ptr 类也没有重写 operator! 操作,因此也不能通过 std::weak_ptr 对象直接判断其引用的资源是否存在,示例如下:
class A
{
public:
void doSomething()
{
}
};
int main()
{
std::shared_ptr<A> object1(new A());
std::weak_ptr<A> object2(object1);
//正确代码
if (object1)
{
//正确代码
object1->doSomething();
(*object1).doSomething();
}
//正确代码
if (!object1)
{
}
//错误代码,无法编译通过
//if (object2)
//{
// //错误代码,无法编译通过
// object2->doSomething();
// (*object2).doSomething();
//}
//错误代码,无法编译通过
//if (!object2)
//{
//}
return 0;
}
weak_ptr一般通过share_ptr来构造,通过expired函数检查原始指针是否为空,lock来转化为share_ptr。
<4>std::weak_ptr常用API
//将当前 weak_ptr 对象的内容与 x 的内容交换。
void swap (weak_ptr& x)
//将当前 weak_ptr 对象管理的指针和计数器变成空的,就像默认构造的一样。
void reset()
//返回与当前 weak_ptr 对象在同一指针上共享所有权的 shared_ptr 对象的数量。
long int use_count()
//检查是否过期,返回 weak_ptr 对象管理的指针为空,或者和他所属共享的没有更多 shared_ptr。lock 函数一般需要先调用 expired 判断,如果已经过期,就不能通过 weak_ptr 恢复拥有的 shared_ptr。此函数应返回与(use_count() == 0)相同的值,但是它可能以更有效的方式执行此操作。
bool expired()
//如果它没有过期,则返回一个 shared_ptr,其中包含由 weak_ptr 对象保留的信息。如果 weak_ptr 对象已经过期,则该函数返回一个空的 shared_ptr(默认构造一样)。因为返回的 shared_ptr 对象也算作一个所有者,所以这个函数锁定了拥有的指针,防止它被释放(至少在返回的对象没有释放它的情况下)。 此操作以原子方式执行。
shared_ptr<element_type> lock()
5.智能指针的大小
在 32 位机器上,std_unique_ptr 占 4 字节,std::shared_ptr 和 std::weak_ptr 占 8 字节。
在 64 位机器上,std_unique_ptr 占 8 字节,std::shared_ptr 和 std::weak_ptr 占 16 字节。
👉👉std_unique_ptr 的大小总是和原始指针大小一样,std::shared_ptr 和 std::weak_ptr 大小是原始指针的一倍。
三、智能指针的注意事项
待完善。。。
|