194、智能指针 shared_ptr
shared_ptr 共享它指向的对象,多个 shared_ptr 可以指向(关联)相同的对象,在内部采用计数机
制来实现。
当新的 shared_ptr 与对象关联时,引用计数增加 1。
当 shared_ptr 超出作用域时,引用计数减 1。当引用计数变为 0 时,则表示没有任何 shared_ptr
与对象关联,则释放该对象。
一、基本用法
shared_ptr 的构造函数也是 explicit,但是,没有删除拷贝构造函数和赋值函数。
1)初始化
方法一:
shared_ptr<AA> p0(new AA("西施")); // 分配内存并初始化。
方法二:
shared_ptr<AA> p0 = make_shared<AA>("西施"); // C++11 标准,效率更高。
shared_ptr<int> pp1=make_shared<int>(); // 数据类型为 int。
shared_ptr<AA> pp2 = make_shared<AA>(); // 数据类型为 AA,默认构造函数。
shared_ptr<AA> pp3 = make_shared<AA>("西施"); // 数据类型为 AA,一个参数的构造函数。
shared_ptr<AA> pp4 = make_shared<AA>("西施",8); // 数据类型为 AA,两个参数的构造函数。
方法三:
AA* p = new AA("西施");
shared_ptr<AA> p0(p); // 用已存在的地址初始化。
方法四:
shared_ptr<AA> p0(new AA("西施"));
shared_ptr<AA> p1(p0); // 用已存在的 shared_ptr 初始化,计数加 1。
shared_ptr<AA> p1=p0; // 用已存在的 shared_ptr 初始化,计数加 1。
2)使用方法
智能指针重载了*和->操作符,可以像使用指针一样使用 shared_ptr。
use_count()方法返回引用计数器的值。
unique()方法,如果 use_count()为 1,返回 true,否则返回 false。
shared_ptr 支持赋值,左值的 shared_ptr 的计数器将减 1,右值 shared_ptr 的计算器将加 1。
get()方法返回裸指针。
不要用同一个裸指针初始化多个 shared_ptr。
不要用 shared_ptr 管理不是 new 分配的内存。
3)用于函数的参数
与 unique_ptr 的原理相同。
4)不支持指针的运算(+、-、++、--)
二、更多细节
1)将一个 unique_ptr 赋给另一个时,如果源 unique_ptr 是一个临时右值,编译器允许这样做;如
果源 unique_ptr 将存在一段时间,编译器禁止这样做。一般用于函数的返回值。
2)用nullptr给shared_ptr赋值将把计数减1,如果计数为0,将释放对象,空的shared_ptr==nullptr。
3)release()释放对原始指针的控制权,将 unique_ptr 置为空,返回裸指针。
4)std::move()可以转移对原始指针的控制权。还可以将 unique_ptr 转移成 shared_ptr。
5)reset()改变与资源的关联关系。
pp.reset(); // 解除与资源的关系,资源的引用计数减 1。
pp. reset(new AA("bbb")); // 解除与资源的关系,资源的引用计数减 1。关联新资源。
6)swap()交换两个 shared_ptr 的控制权。
void swap(shared_ptr<T> &_Right);
7)shared_ptr 也可象普通指针那样,当指向一个类继承体系的基类对象时,也具有多态性质,如同
使用裸指针管理基类对象和派生类对象那样。
8)shared_ptr 不是绝对安全,如果程序中调用 exit()退出,全局的 shared_ptr 可以自动释放,但局
部的 shared_ptr 无法释放。
9)shared_ptr 提供了支持数组的具体化版本。
数组版本的 shared_ptr,重载了操作符[],操作符[]返回的是引用,可以作为左值使用。
10)shared_ptr 的线程安全性:
shared_ptr 的引用计数本身是线程安全(引用计数是原子操作)。
多个线程同时读同一个 shared_ptr 对象是线程安全的。
如果是多个线程对同一个 shared_ptr 对象进行读和写,则需要加锁。
多线程读写 shared_ptr 所指向的同一个对象,不管是相同的 shared_ptr 对象,还是不同的
shared_ptr 对象,也需要加锁保护。
11)如果 unique_ptr 能解决问题,就不要用 shared_ptr。unique_ptr 的效率更高,占用的资源更
少。
示例 1:
#include <iostream>
#include <memory>
using namespace std;
class AA
{
public:
string m_name;
AA() { cout << m_name << "调用构造函数 AA()。\n"; }
AA(const string & name) : m_name(name) { cout << "调用构造函数 AA("<< m_name <<
")。\n"; }~AA() { cout << "调用了析构函数~AA(" << m_name << ")。\n"; }
};
int main()
{
shared_ptr<AA> pa0(new AA("西施 a")); // 初始化资源西施 a。
shared_ptr<AA> pa1 = pa0; // 用已存在的 shared_ptr 拷贝构造,
计数加 1。
shared_ptr<AA> pa2 = pa0; // 用已存在的 shared_ptr 拷贝构造,
计数加 1。
cout << "pa0.use_count()=" << pa0.use_count() << endl; // 值为 3。
shared_ptr<AA> pb0(new AA("西施 b")); // 初始化资源西施 b。
shared_ptr<AA> pb1 = pb0; // 用已存在的 shared_ptr 拷贝构造,
计数加 1。
cout << "pb0.use_count()=" << pb0.use_count() << endl; // 值为 2。
pb1 = pa1; // 资源西施 a 的引用加 1,资源西施 b 的引用减 1。
pb0 = pa1; // 资源西施 a 的引用加 1,资源西施 b 的引用成了 0,将被释放。
cout << "pa0.use_count()=" << pa0.use_count() << endl; // 值为 5。
cout << "pb0.use_count()=" << pb0.use_count() << endl; // 值为 5。
}
195、智能指针的删除器
在默认情况下,智能指针过期的时候,用 delete 原始指针; 释放它管理的资源。
程序员可以自定义删除器,改变智能指针释放资源的行为。
删除器可以是全局函数、仿函数和 Lambda 表达式,形参为原始指针。
示例:
#include <iostream>
#include <memory>
using namespace std;
class AA
{
public:
string m_name;
AA() { cout << m_name << "调用构造函数 AA()。\n"; }
AA(const string & name) : m_name(name) { cout << "调用构造函数 AA("<< m_name <<
")。\n"; }~AA() { cout << "调用了析构函数~AA(" << m_name << ")。\n"; }
};
void deletefunc(AA* a) { // 删除器,普通函数。
cout << "自定义删除器(全局函数)。\n";
delete a;
}
struct deleteclass // 删除器,仿函数。
{
void operator()(AA* a) {
cout << "自定义删除器(仿函数)。\n";
delete a;
}
};
auto deleterlamb = [](AA* a) { // 删除器,Lambda 表达式。
cout << "自定义删除器(Lambda)。\n";
delete a;
};
int main()
{
shared_ptr<AA> pa1(new AA("西施 a"), deletefunc);
//shared_ptr<AA> pa2(new AA("西施 b"), deleteclass());
//shared_ptr<AA> pa3(new AA("西施 c"), deleterlamb);
//unique_ptr<AA,decltype(deletefunc)*> pu1(new AA("西施 1"), deletefunc);
// unique_ptr<AA, void (*)(AA*)> pu0(new AA("西施 1"), deletefunc);
//unique_ptr<AA, deleteclass> pu2(new AA("西施 2"), deleteclass());
//unique_ptr<AA, decltype(deleterlamb)> pu3(new AA("西施 3"), deleterlamb);
}
196、智能指针 weak_ptr 一、shared_ptr 存在的问题
shared_ptr 内部维护了一个共享的引用计数器,多个 shared_ptr 可以指向同一个资源。
如果出现了循环引用的情况,引用计数永远无法归 0,资源不会被释放。
示例:
#include <iostream>
#include <memory>
using namespace std;
class BB;
class AA
{
public:
string m_name;
AA() { cout << m_name << "调用构造函数 AA()。\n"; }
AA(const string & name) : m_name(name) { cout << "调用构造函数 AA("<< m_name <<
")。\n"; }~AA() { cout << "调用了析构函数~AA(" << m_name << ")。\n"; }
shared_ptr<BB> m_p;
};
class BB
{
public:
string m_name;
BB() { cout << m_name << "调用构造函数 BB()。\n"; }
BB(const string& name) : m_name(name) { cout << "调用构造函数 BB(" << m_name << ")。
\n"; } ~BB() { cout << "调用了析构函数~BB(" << m_name << ")。\n"; }
shared_ptr<AA> m_p;
};
int main()
{
shared_ptr<AA> pa = make_shared<AA>("西施 a");
shared_ptr<BB> pb = make_shared<BB>("西施 b");
pa-> m_p = pb;
pb->m_p = pa;
}
二、weak_ptr 是什么
weak_ptr 是为了配合 shared_ptr 而引入的,它指向一个由 shared_ptr 管理的资源但不影响资源的
生命周期。也就是说,将一个 weak_ptr 绑定到一个 shared_ptr 不会改变 shared_ptr 的引用计数。
不论是否有 weak_ptr 指向,如果最后一个指向资源的 shared_ptr 被销毁,资源就会被释放。
weak_ptr 更像是 shared_ptr 的助手而不是智能指针。
示例:
#include <iostream>
#include <memory>
using namespace std;
class BB;
class AA
{
public:
string m_name;
AA() { cout << m_name << "调用构造函数 AA()。\n"; }
AA(const string & name) : m_name(name) { cout << "调用构造函数 AA("<< m_name <<
")。\n"; }~AA() { cout << "调用了析构函数~AA(" << m_name << ")。\n"; }
weak_ptr<BB> m_p;
};
class BB
{
public:
string m_name;
BB() { cout << m_name << "调用构造函数 BB()。\n"; }
BB(const string& name) : m_name(name) { cout << "调用构造函数 BB(" << m_name << ")。
\n"; } ~BB() { cout << "调用了析构函数~BB(" << m_name << ")。\n"; }
weak_ptr<AA> m_p;
};
int main()
{
shared_ptr<AA> pa = make_shared<AA>("西施 a");
shared_ptr<BB> pb = make_shared<BB>("西施 b");
cout << "pa.use_count()=" << pa.use_count() << endl;
cout << "pb.use_count()=" << pb.use_count() << endl;
pa->m_p = pb;
pb->m_p = pa;
cout << "pa.use_count()=" << pa.use_count() << endl;
cout << "pb.use_count()=" << pb.use_count() << endl;
}
三、如何使用 weak_ptr
weak_ptr 没有重载 ->和 *操作符,不能直接访问资源。
有以下成员函数:
1)operator=(); // 把 shared_ptr 或 weak_ptr 赋值给 weak_ptr。
2)expired(); // 判断它指资源是否已过期(已经被销毁)。
3)lock(); // 返回 shared_ptr,如果资源已过期,返回空的 shared_ptr。
4)reset(); // 将当前 weak_ptr 指针置为空。
5)swap(); // 交换。
weak_ptr 不控制对象的生命周期,但是,它知道对象是否还活着。
用 lock()函数把它可以提升为 shared_ptr,如果对象还活着,返回有效的 shared_ptr,如果对象
已经死了,提升会失败,返回一个空的 shared_ptr。
提升的行为(lock())是线程安全的。
示例:
#include <iostream>
#include <memory>
using namespace std;
class BB;
class AA
{
public:
string m_name;
AA() { cout << m_name << "调用构造函数 AA()。\n"; }
AA(const string& name) : m_name(name) { cout << "调用构造函数 AA(" << m_name <<
")。\n"; }
~AA() { cout << "调用了析构函数~AA(" << m_name << ")。\n"; }
weak_ptr<BB> m_p;
};
class BB
{
public:
string m_name;
BB() { cout << m_name << "调用构造函数 BB()。\n"; }
BB(const string& name) : m_name(name) { cout << "调用构造函数 BB(" << m_name << ")。
\n"; } ~BB() { cout << "调用了析构函数~BB(" << m_name << ")。\n"; }
weak_ptr<AA> m_p;
};
int main()
{
shared_ptr<AA> pa = make_shared<AA>("西施 a");
{
shared_ptr<BB> pb = make_shared<BB>("西施 b");
pa->m_p = pb;
pb->m_p = pa;
shared_ptr<BB> pp = pa->m_p.lock(); // 把 weak_ptr 提升为 shared_ptr。
if (pp == nullptr)
cout << "语句块内部:pa->m_p 已过期。\n";
else
cout << "语句块内部:pp->m_name=" << pp->m_name << endl;
}
shared_ptr<BB> pp = pa->m_p.lock(); // 把 weak_ptr 提升为 shared_ptr。
if (pp == nullptr)
cout << "语句块外部:pa->m_p 已过期。\n";
else
cout << "语句块外部:pp->m_name=" << pp->m_name << endl;
}
201、文件操作-写入文本文件
文本文件一般以行的形式组织数据。
包含头文件:#include <fstream>
类:ofstream(output file stream)
ofstream 打开文件的模式(方式):
对于 ofstream,不管用哪种模式打开文件,如果文件不存在,都会创建文件。
ios::out 缺省值:会截断文件内容。
ios::trunc 截断文件内容。(truncate)
ios::app 不截断文件内容,只在文件未尾追加文件。(append)
示例:
#include <iostream>
#include <fstream> // ofstream 类需要包含的头文件。
using namespace std;
int main()
{
// 文件名一般用全路径,书写的方法如下:
// 1)"D:\data\txt\test.txt" // 错误。
// 2)R"(D:\data\txt\test.txt)" // 原始字面量,C++11 标准。
// 3)"D:\\data\\txt\\test.txt" // 转义字符。
// 4)"D:/tata/txt/test.txt" // 把斜线反着写。
// 5)"/data/txt/test.txt" // Linux 系统采用的方法。
string filename = R"(D:\data\txt\test.txt)";
//char filename[] = R"(D:\data\txt\test.txt)";
// 创建文件输出流对象,打开文件,如果文件不存在,则创建它。
// ios::out 缺省值:会截断文件内容。
// ios::trunc 截断文件内容。(truncate)
// ios::app 不截断文件内容,只在文件未尾追加文件。(append)
//ofstream fout(filename);
//ofstream fout(filename, ios::out);
//ofstream fout(filename, ios::trunc);
//ofstream fout(filename, ios::app);
ofstream fout;
fout.open(filename,ios::app);
// 判断打开文件是否成功。
// 失败的原因主要有:1)目录不存在;2)磁盘空间已满;3)没有权限,Linux 平台下很常见。
if (fout.is_open() == false)
{
cout << "打开文件" << filename << "失败。\n"; return 0;
}
// 向文件中写入数据。
fout << "西施|19|极漂亮\n";
fout << "冰冰|22|漂亮\n";
fout << "幂幂|25|一般\n";
fout.close(); // 关闭文件,fout 对象失效前会自动调用 close()。
cout << "操作文件完成。\n";
}
202、文件操作-读取文本文件
包含头文件:#include <fstream>
类:ifstream
ifstream 打开文件的模式(方式):
对于 ifstream,如果文件不存在,则打开文件失败。
ios::in 缺省值。
示例:
#include <iostream>
#include <fstream> // ifstream 类需要包含的头文件。
#include <string> // getline()函数需要包含的头文件。
using namespace std;
int main()
{
// 文件名一般用全路径,书写的方法如下:
// 1)"D:\data\txt\test.txt" // 错误。
// 2)R"(D:\data\txt\test.txt)" // 原始字面量,C++11 标准。
// 3)"D:\\data\\txt\\test.txt" // 转义字符。
// 4)"D:/tata/txt/test.txt" // 把斜线反着写。
// 5)"/data/txt/test.txt" // Linux 系统采用的方法。
string filename = R"(D:\data\txt\test.txt)";
//char filename[] = R"(D:\data\txt\test.txt)";
// 创建文件输入流对象,打开文件,如果文件不存在,则打开文件失败。。
// ios::in 缺省值。
//ifstream fin(filename);
//ifstream fin(filename, ios::in);
ifstream fin;
fin.open(filename,ios::in);
// 判断打开文件是否成功。
// 失败的原因主要有:1)目录不存在;2)文件不存在;3)没有权限,Linux 平台下很常见。
if (fin.is_open() == false)
{
cout << "打开文件" << filename << "失败。\n"; return 0;
}
第一种方法。
//string buffer; // 用于存放从文件中读取的内容。
文本文件一般以行的方式组织数据。
//while (getline(fin, buffer))
//{
// cout << buffer << endl;
//}
第二种方法。
//char buffer[16]; // 存放从文件中读取的内容。
注意:如果采用 ifstream.getline(),一定要保证缓冲区足够大。
//while (fin.getline(buffer, 15))
//{
// cout << buffer << endl;
//}
// 第三种方法。
string buffer;
while (fin >> buffer)
{
cout << buffer << endl;
}
fin.close(); // 关闭文件,fin 对象失效前会自动调用 close()。
cout << "操作文件完成。\n";
}