所有的智能指针都是基于模板泛型
一.auto_ptr
C++17里面废除了,C++11标记废弃,弃用的原因是存在重大的缺陷和安全问题
1.用法
#include <iostream>
#include <vector>
using namespace std;
class Person
{
public:
Person()
{
cout << "Person 构造函数" << endl;
}
~Person()
{
cout << "Person 析构函数" << endl;
}
};
void test()
{
auto_ptr<Person> ptr(new Person);
// auto_ptr 不适合用于管理动态内存的数组对象。
//auto_ptr<Person[]> ptr(new Person[10]);
// 获得资源指针
cout << ptr.get() << endl;
// 释放资源所有权,即:ptr 不再持有动态资源
Person* p = ptr.release();
// 释放资源,并指向新的资源
ptr.reset(new Person);
// 只释放资源
ptr.reset();
}
int main()
{
test();
return 0;
}
2.注意:
1.auto_ptr底层使用的是delete,所以这是不合适管理动态的内存数组对象;
2.ptr.release释放资源的所有权,那么原来的对象需要我们自己手动释放;
3.auto_ptr缺陷
1.auto_ptr不能共享所有权,多个指针不能指向同一个对象
2.auto_ptr内部没有拷贝构造函数的,就不能放在容器里面,auto_ptr在拷贝或者赋值 时进行的是资源所有权转移,而并不是真正的拷贝和赋值,会发生所有权转移
// 1. 不能共享所有权
void function(auto_ptr<Person> person) {}
void test01()
{
auto_ptr<Person> ptr1(new Person);
// 将 ptr1 传递到 ptr2 的有参构造中进行初始化
auto_ptr<Person> ptr2 = ptr1;
cout << "ptr1:" << ptr1.get() << " ptr2:" << ptr2.get() << endl;
// 赋值发生所有权转移
auto_ptr<Person> ptr3;
ptr3 = ptr2;
cout << "ptr2:" << ptr2.get() << " ptr3:" << ptr3.get() << endl;
function(ptr3);
cout << "ptr3:" << ptr3.get() << endl;
}
不能放在容器
auto_ptr<Person> ptr1(new Person);
auto_ptr<Person> ptr2(new Person);
// 容器要求元素要能够被拷贝
vector<auto_ptr<Person>> vec;
// 左值
// vec.push_back(ptr1);
// vec.push_back(ptr2);
// 添加右值(临时对象)
vec.push_back(auto_ptr<Person>(new Person));
auto v = vec[0];
cout << vec[0].get() << endl;
会报错,所以不能作为容器的元素
添加右值就不会报错,但是也是会有问题
2.unique:独占所有权管理指针,不能多个指针指向不同的对象,类似于auto_ptr;
1.用法
#include <iostream>
using namespace std;
class Person
{
public:
Person()
{
cout << "构造函数" << endl;
}
void Demo()
{
cout << "Person Demo" << endl;
}
~Person()
{
cout << "析构函数" << endl;
}
};
// 1. unique_ptr 创建方式
void test01()
{
// 1.1 管理单一动态对象
unique_ptr<Person> up1(new Person);
up1->Demo();
// 1.2 管理动态对象数组
unique_ptr<Person[]> up2(new Person[2]);
up2[0].Demo();
up2[1].Demo();
}
// 2. unique_ptr 操作函数
void test02()
{
unique_ptr<Person> up(new Person);
// 2.1 get 成员函数返回 unique_ptr 管理的动态对象指针
Person* person1 = up.get();
// 2.2 release 成员函数使 unique_ptr 不再持有动态对象指针(并不销毁管理的对象),并返回其管理的动态指针。
Person* person2 = up.release();
delete person2;
// 2.3 reset 成员函数有两个重载的版本,具体功能如下:
up.reset(); // 释放并销毁 unique_ptr 所管理的动态对象指针。
up.reset(new Person); // 释放并销毁原来的管理的动态对象,并重新持有新创建的动态对象
// 2.4 swap 成员函数交换两个 unique_ptr 对象管理的动态对象
unique_ptr<Person> sp(new Person);
up.swap(sp);
}
int main()
{
// test01();
test02();
return 0;
}
2.特性
- 同时只能有一个 unique_ptr 对象来持有动态对象资源
- unique_ptr 对象不支持默认拷贝、默认赋值语义
- unique_ptr 对象支持移动拷贝、移动赋值语义
- unique_ptr 对象不能以值的方式用做函数参数,也不能存储到STL 的容器中
#include <iostream>
#include <memory>
#include <vector>
using namespace std;
class Person
{
public:
Person()
{
cout << "构造函数" << endl;
}
~Person()
{
cout << "析构函数" << endl;
}
};
void test()
{
unique_ptr<Person> up1(new Person);
unique_ptr<Person> up2(new Person);
// 1. 禁止拷贝、赋值
// unique_ptr(const unique_ptr&) = delete;
// unique_ptr& operator=(const unique_ptr&) = delete;
// 2. 允许移动拷贝、赋值
unique_ptr<Person> up3(move(up1)); // 移动拷贝
up2 = move(up3); // 移动赋值
// 由此, unique_ptr 不允许作为容器元素
// vector<unique_ptr<Person>> vec;
// vec.push_back(up1);
}
int main()
{
test();
return 0;
}
拷贝赋值的时候编译器会报错,明确禁止的
3.unique_ptr 自定义删除器
unique_ptr 可用于管理 new 出来的动态对象,也可以管理其他非new需要手动关闭的资源。例如:文件对象。
由于 unique_ptr 默认使用 delete、delete[] 来释放被管理的资源。所以,当管理的对象不能通过 delete、delete[] 来释放时,就需要自定义删除器
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <functional>
using namespace std;
class Person
{
public:
Person()
{
cout << "构造函数" << endl;
}
~Person()
{
cout << "析构函数" << endl;
}
};
struct Deleter
{
void operator()(FILE* fp)
{
cout << "文件被自动关闭" << endl;
if (fp != nullptr)
{
fclose(fp);
fp = nullptr;
}
}
};
void my_deleter(FILE* fp)
{
cout << "文件被自动关闭" << endl;
if (fp != nullptr)
{
fclose(fp);
fp = nullptr;
}
}
void test()
{
// 1. 函数对象作为删除器
// unique_ptr<FILE, Deleter> up(fopen("./demo.txt", "w"), Deleter());
// unique_ptr<FILE, function<void(FILE*)>> up(fopen("./demo.txt", "w"), Deleter());
// 2. 普通函数作为删除器
// unique_ptr<FILE, decltype(&my_deleter)> up(fopen("./demo.txt", "w"), my_deleter);
// unique_ptr<FILE, void(*)(FILE *)> up(fopen("./demo.txt", "w"), my_deleter);
// unique_ptr<FILE, function<void(FILE*)>> up(fopen("./demo.txt", "w"), my_deleter);
// 3. 匿名函数作为删除器
unique_ptr<FILE, function<void(FILE *)>> up(fopen("./demo.txt", "w"), [](FILE *fp) {
cout << "文件被自动关闭" << endl;
if (fp != nullptr)
{
fclose(fp);
fp = nullptr;
}
});
if (!up)
{
cout << "文件打开失败" << endl;
return;
}
fputs("hello world\n", up.get());
}
int main()
{
test();
return 0;
}
3.shared_ptr
C++ 的 shared_ptr
是 C++11 标准引入的智能指针之一,用于管理动态分配的对象的所有权。它允许多个 shared_ptr
实例共享对同一对象的所有权,而不会出现内存泄漏或者悬空指针的情况。shared_ptr
使用引用计数技术来跟踪有多少个 shared_ptr
实例指向同一个对象,并在最后一个实例销毁时自动释放对象;
1.用法
#include <iostream>
using namespace std;
class Person
{
public:
Person()
{
cout << "无参构造函数" << endl;
}
Person(int, int)
{
cout << "有参构造函数" << endl;
}
~Person()
{
cout << "析构函数" << endl;
}
};
// 1. 创建 shared_ptr 对象
void test01()
{
// 1.1 使用 shared_ptr 的构造函数创建对象
shared_ptr<Person> sp1(new Person(10, 20));
// 1.2 使用 shared_ptr 管理动态对象数组
shared_ptr<Person[]> sp2(new Person[5]);
}
// 2. shared_ptr 操作函数
void test02()
{
shared_ptr<Person> sp1(new Person(10, 20));
shared_ptr<Person> sp2(new Person(100, 200));
// 2.1 get 成员函数可以获得 shared_prt 管理的动态对象指针
Person* person = sp1.get();
// 2.2 swap 成员函数可以交换两个 shared_ptr 管理的动态对象指针
sp1.swap(sp2);
// 2.3 reset 成员函数存在两个重载版本的函数,其作用分别如下:
sp1.reset(); // 释放其管理动态指针,此时 sp1 对象管理的动态指针指向为 nullptr
sp1.reset(new Person(1, 2)); // 释放原来的动态对象,并指向新的动态对象
}
int main()
{
test01();
test02();
return 0;
}
2.特性
shared_ptr 叫做共享智能指针,多个 shared_ptr 对象能够同时持有并管理同一个动态对象。当 shared_ptr 发生对象拷贝、赋值时都会导致多个 shared_ptr 对象持有同一个动态对象
#include <iostream>
#include <vector>
using namespace std;
class Person
{
public:
Person()
{
cout << "无参构造函数" << endl;
}
~Person()
{
cout << "析构函数" << endl;
}
};
void test()
{
shared_ptr<Person> sp1(new Person);
// 1. 允许对象拷贝、对象赋值
shared_ptr<Person> sp2(sp1);
shared_ptr<Person> sp3;
sp3 = sp1; // 对象赋值
// 2. 允许对象移动拷贝、移动赋值
shared_ptr<Person> sp4 = move(sp3);
shared_ptr<Person> sp5;
sp5 = move(sp1); // 移动赋值
cout << "sp1:" << sp1.get() << endl; // 已被移动
cout << "sp2:" << sp2.get() << endl;
cout << "sp3:" << sp3.get() << endl; // 已被移动
cout << "sp4:" << sp4.get() << endl;
cout << "sp5:" << sp5.get() << endl;
// 允许存储到容器中
vector<shared_ptr<Person>> vec;
vec.push_back(sp2);
vec.push_back(sp4);
vec.push_back(sp5);
}
int main()
{
test();
return 0;
}
3.引用计数
多个 shared_ptr 管理同一个 Person 对象,当某个 shared_ptr 生命周期结束,不会导致其他的 shared_ptr 管理了无效指针吗?
答案是不会的。这是因为 shared_ptr 是通过引用计数的方式来实现对象共享的。那么,什么是引用计数呢?
在 shared_ptr 对象的内部维护了两个非常重要的内容:
- 动态创建对象
- 引用计数对象
#include <iostream>
using namespace std;
struct Person
{
Person()
{
cout << "Person 构造函数" << endl;
}
~Person()
{
cout << "Person 析构函数" << endl;
}
};
void test()
{
// 初始化智能指针,引用计数为 0
shared_ptr<Person> sp1(new Person);
cout << "sp1:" << sp1.use_count() << endl;
// 发生拷贝,引用计数 +1
shared_ptr<Person> sp2(sp1);
cout << "sp2:" << sp2.use_count() << endl;
// 发生赋值,引用计数 + 1
shared_ptr<Person> sp3;
sp3 = sp2;
cout << "sp3:" << sp3.use_count() << endl;
// 判断是否独占资源
cout << "sp1 是否独占资源:" << sp1.unique() << endl;
// sp2 释放资源所有权,通过该对方访问的引用次数为 0
sp2.reset();
cout << "sp2:" << sp2.use_count() << endl;
// sp1 和 sp2 引用计数为 2
cout << "sp1:" << sp1.use_count() << endl;
cout << "sp3:" << sp3.use_count() << endl;
}
int main()
{
test();
return 0;
}
共享指针通常运行在多线程程序中,其中多个指针可能会从不同线程更新同一个引用计数器,计数器是以原子方式实现,或者使用互斥锁来防止数据竞争。引用计数是原子安全的。
引用计数什么时候会发生变化
- 每次调用共享对象的复制构造函数时,共享指针都会递增计数器。
- 每次调用共享对象的赋值运算符时,右侧指针都会递增其计数器,左侧指针会递减。
- 每次调用共享对象的析构函数时,都会减少其计数器。
- 计数器为0时,则删除该对象。
4.自定义删除器
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <memory>
using namespace std;
void my_deleter(FILE* fp)
{
cout << "文件自动关闭" << endl;
fclose(fp);
fp = nullptr;
}
struct MyDeleter
{
void operator()(FILE* fp)
{
cout << "文件自动关闭" << endl;
fclose(fp);
fp = nullptr;
}
};
void test()
{
// 1. 使用普通函数作为自定义删除器
shared_ptr<FILE> sp1(fopen("./demo.txt", "w"), my_deleter);
// 2. 使用函数对象作为自定义删除器
shared_ptr<FILE> sp2(fopen("./demo.txt", "w"), MyDeleter());
// 3. 使用 lambda 匿名函数对象作为自定义删除器
shared_ptr<FILE> sp3(fopen("./demo.txt", "w"), [](FILE* fp) {
cout << "文件自动关闭" << endl;
fclose(fp);
fp = nullptr;
});
}
int main()
{
test();
return 0;
}
4,weak_ptr
std::weak_ptr
是 C++ 标准库中的一个智能指针类,用于解决 std::shared_ptr
可能引发的循环引用问题。循环引用可能导致内存泄漏,因为引用计数无法降为零,从而无法释放对象。
std::weak_ptr
是一种弱引用,它允许你观测由 std::shared_ptr
管理的对象,但不会增加对象的引用计数。换句话说,std::weak_ptr
不拥有所指向对象的所有权,因此不会影响对象的生命周期。当 std::shared_ptr
管理的对象被销毁后,对应的 std::weak_ptr
会自动失效,指向空值
1.用法
weak_ptr 是作为 shared_ptr 辅助角色存在,不会被直接用来管理动态对象。所以:
- 不会直接创建 weak_ptr 去管理动态对象
- weak_ptr 只能通过 shared_ptr 对象创建
- weak_ptr 引用 shared_ptr 对象时,并不会增加引用计数
- weak_ptr 不直接操作 shared_ptr 管理的对象,但允许间接操作 shared_ptr 管理的对象
#include <iostream>
using namespace std;
class Person
{
public:
Person(int, int)
{
cout << "构造函数" << endl;
}
void show()
{
cout << "Person::show 函数" << endl;
}
~Person()
{
cout << "析构函数" << endl;
}
};
void test()
{
// weak_ptr 是对 shared_ptr 的辅助,其自身并不拥有资源所有权
shared_ptr<Person> sp1 = make_shared<Person>(10, 20);
// 通过拷贝 shared_ptr 对象创建 weak_ptr 对象
weak_ptr<Person> wp1(sp1);
// weak_ptr 使用时,不能直接访问对象成员
// 必须使用 lock 方法返回一个 shared_ptr 对象(会增加引用)
if (wp1.expired())
{
return;
}
//返回一个新的shared_ptr,会增加引用技术
auto sp2 = wp1.lock();
sp2->show();
// 可以将 shared_ptr 赋值给 weak_ptr 对象
weak_ptr<Person> wp2;
wp2 = sp1;
// weak_ptr 相当于一个不增加引用的 shared_ptr
cout << "sp1:" << sp1.use_count() << endl;
cout << "sp2:" << sp2.use_count() << endl;
cout << "wp1:" << wp1.use_count() << endl;
cout << "wp2:" << wp2.use_count() << endl;
}
int main()
{
test();
return 0;
}
2.循环引用
#include <iostream>
using namespace std;
// 双向链表节点
class LinkNode
{
public:
LinkNode(int value)
{
cout << "LinkNode 构造函数" << endl;
data = value;
}
~LinkNode()
{
cout << "LinkNode 析构函数" << endl;
}
public:
int data;
shared_ptr<LinkNode> prev;
shared_ptr<LinkNode> next;
};
void test()
{
// 创建两个链表节点
shared_ptr<LinkNode> node1(new LinkNode(10));
shared_ptr<LinkNode> node2(new LinkNode(20));
// 建立节点之间的关系
node1->next = node2;
node2->prev = node1;
}
int main()
{
test();
return 0;
}
会发现析构函数没有被调用
- 由于循环引用,导致两个 LinkNode 关联的引用计数为 2
- 当 test 函数生命周期结束,node1 和 node2 的析构函数调用,两个 LinkNode 对象引用计数 -1,引用计数为 1
- 只有当 LinkNode 实际被销毁时,才会调用 prev 和 next 的析构函数
3.weak_ptr 使用示例
当一个对象持有对另一个对象的引用,而后者又持有对前者的引用时,就会产生循环引用。所以,要打破这种循环的话,可以使用在发生循环引用的地方使用弱引用
#include <iostream>
using namespace std;
// 双向链表节点
class LinkNode
{
public:
LinkNode(int value)
{
cout << "LinkNode 构造函数" << endl;
data = value;
}
~LinkNode()
{
cout << "LinkNode 析构函数" << endl;
}
public:
int data;
// 使用 weak_ptr 代替 shared_ptr
weak_ptr<LinkNode> prev;
weak_ptr<LinkNode> next;
};
void test()
{
// 创建两个链表节点
shared_ptr<LinkNode> node1(new LinkNode(10));
shared_ptr<LinkNode> node2(new LinkNode(20));
// 建立节点之间的关系
node1->next = node2;
node2->prev = node1;
}
int main()
{
test();
return 0;
}