智能指针
为了更安全地使用动态内存(避免内存泄漏),引入了智能指针。
shared_ptr
一般来说,p->call
是调用智能指针指向对象的成员,p.call
是调用智能指针的api。
make_shared
make_shared
用参数来构造给定类型的对象,其()内的参数列表必须与某个构造函数相匹配。
struct Node {
int v;
Node *left, *right;
string s;
void show() {
cout << "v is " << v << endl;
}
Node(int i) : v(i), left(nullptr), right(nullptr), s("") {}
Node(int i, string t) : v(i), left(nullptr), right(nullptr), s(t) {}
};
int main() {
// 错误,shared_ptr的构造函数时explicit的,禁止了内置指针隐式转换为智能指针,只能使用直接初始化形式
// shared_ptr<Node> p = new Node(1);
shared_ptr<Node> p1 = make_shared<Node>(1);
shared_ptr<Node> p2 = make_shared<Node>(1, "Hello");
shared_ptr<Node> p3(make_shared<Node>(1));
auto p4 = make_shared<Node>(1, "AA");
}
shared_ptr的拷贝和赋值
当p = q
时,p的引用计数减一,q的引用计数加一。
当一个shared_ptr被销毁时,计数器也会递减。当计数为0时,释放内存。
note 如果将shared_ptr存放于某个容器,当不再使用后记得erase,不然仍会存在内存泄漏。
使用动态生存期的资源的类
使用动态内存的原因:
- 程序不知道自己需要多少对象(如vector)
- 程序不知道对象的准确类型
- 程序需要在多个对象间共享数据
vector<string> v1;
{
// 新作用域
vector<string> v2 = {"a", "b"};
v1 = v2;
// v1拷贝了v2中的元素,在作用域结束后,v2的元素被释放
}
有时候我们希望不同的对象共享底层数据,即v2被销毁后,其元素仍被保留,因为v1在使用它。
这个时候将底层数据结构声明为shared_ptr即可
class strBlob {
private:
using size_type = vector<string>::size_type;
shared_ptr<vector<string>> cache;
public:
strBlob() : cache(make_shared<vector<string>>()) {}
strBlob(initializer_list<string> il) : cache(make_shared<vector<string>>(il)) {}
void push_back(const string &s) {
cache->push_back(s);
}
string &back() {
return cache->back();
}
string &front() {
return cache->front();
}
void pop_back() {
cache->pop_back();
}
size_type size() {
return cache->size();
}
bool empty() {
return cache->empty();
}
void show() {
cout << "Show items :" << endl;
for (auto i: *cache) {
cout << i << " ";
}
cout << endl;
}
};
int main() {
strBlob b;
{
strBlob a({"Hello", "my", "name", "?"});
a.pop_back();
a.push_back("is");
a.push_back("Tom");
b = a;
}
// a被释放了
b.show();
/*
Show items :
Hello my name is Tom
*/
}
直接控制内存
int *p1 = new int; // 默认初始化,*p1的值未定义
int *p2 = new int(); // 值初始化,*p2的值为0
内存耗尽 当分配空间失败的时候,new表达式会抛出bad_alloc
的异常。 可以通过nothrow Node *q = new(nothrow) Node();
设置,当分配失败时,阻止抛出异常,而返回一个空指针。
这种形式的new称为placement new(定位new),其允许向new传递额外的参数
不要混用普通指针和智能指针
int main() {
Node *p = new Node();
shared_ptr<Node> q(new Node(1, "Hello"));
// 不能将内置指针隐式转换为智能指针
// show(p);
show(q);
// show(new Node(1, "A"));
}
不要使用get初始化其他智能指针
永远保证:智能指针的get(),只在需要传递内置指针的时候使用,并且保证传递过去的指针不被释放,如delete,否则会造成未定义行为
int main() {
// p引用计数为1
shared_ptr<Node> p(make_shared<Node>(1, "A"));
// q指向p保存的指针
Node *q = p.get();
{
// 两个独立的智能指针指向了同一个内存
shared_ptr<Node> i(q);
cout << "size " << i.use_count() << endl; // 1
}
// 智能指针i释放,其引用计数为0,所以释放了指向的内存
// 未定义行为,这里p指向的内存已经被释放了
// show(p);
// cout << "p :";
// cout << "size " << p.use_count() << endl;
}
自定义释放器
我们可以对智能指针自定义释放器,特别是在使用智能指针管理没有析构函数的哑类、管理非new对象时,更有用
void de(Node *a) {
cout << "delete" << endl;
}
int main() {
Node a(1, "H");
{
shared_ptr<Node> p(&a, de);
}
{
shared_ptr<Node> p(&a, [](Node *a) {
cout << "Delete : " << a->val << " " << a->s << endl;
});
}
{
Node a[3] = {Node(1, "A"), Node(2, "B"), Node(3, "C")};
shared_ptr<Node> p(a, [](Node a[3]) {
for (int i = 0; i < 3; i++) {
cout << "Delete : " << a[i].val << " " << a[i].s << endl;
}
});
}
}
/*
delete
Delete : 1 H
Delete : 1 A
Delete : 2 B
Delete : 3 C
*/
unique_ptr
unique_ptr只能指向一个对象,被销毁时,其指向的对象也被销毁。
unique_ptr不支持拷贝和赋值。
release 返回当前指向的指针,并将智能指针置空
unique_ptr<Node> p1(new Node(1, "Hello"));
// 返回p1指向的指针,并将p1置空
Node* p = p1.release();
p->show();
// p1->show(); // error,访问了被释放的内存
调用release会切断unique_ptr和之前指针的联系,一般用来初始化另一个智能指针或者赋值。
注意 调用release后只是释放了指针,并不会释放内存,因此注意要管理release后的指针,避免内存泄漏。
unique的删除器
// 指定了删除器,智能指针释放时会调用函数de来释放资源,不再调用Node的析构函数
unique_ptr<Node, void (*)(Node *)> p1(new Node(1, "Hello"), de);
// 默认delete来释放指针,所以会触发Node的析构函数
unique_ptr<Node> p2(new Node(11, "AA"));
weak_ptr
weak_ptr是不控制所指对象生存周期的智能指针,它指向shared_ptr管理的对象,但是不会改变其引用计数。
当最后一个shared_ptr被销毁后,即使weak_ptr仍指向该对象,其依然会被释放。
所以我们不能直接调用weak_ptr的指针,需要使用lock()来获得shared_ptr对象。
int main() {
auto p = make_shared<Node>(1, "A");
weak_ptr<Node> wp = p;
if (auto np = wp.lock()) {
np->show();
} else {
cout << "Have been released\n";
}
p.reset();
if (auto np = wp.lock()) {
np->show();
} else {
cout << "Have been released\n";
}
}
/*
A 1 // np->show
Release A 1 // 析构函数
Have been released
*/
动态数组
new & 数组
int main() {
// 2个未初始化的int
int *p1 = new int[2];
for (int i = 0; i < 2; i++) cout << p1[i] << " ";
cout << "---------" << endl;
// 2个值初始化为0的int
int *p2 = new int[2]();
for (int i = 0; i < 2; i++) cout << p2[i] << " ";
cout << "---------" << endl;
// 3个默认初始化的Node
Node *w = new Node[3];
cout << "---------" << endl;
// 3个默认初始化的Node
Node *p = new Node[3]();
cout << "---------" << endl;
// 3个按相对应构造函数初始化的Node 和 1个默认初始化的Node
Node *q = new Node[4]{Node(), Node(2), Node(3, "A")};
cout << "---------" << endl;
delete[]w;
delete[]p;
delete[]q;
}
/*
1768299584 605
---------
0 0
---------
Construct default 1
Construct default 1
Construct default 1
---------
Construct default 1
Construct default 1
Construct default 1
---------
Construct default 1
Construct 2
Construct A 3
Construct default 1
---------
*/
new大小为0的数组
delete
释放数组时,应该使用delete[] arrayPoint\
释放一个数组的时候,会按照逆序从尾到头释放
Node *q = new Node[4]{Node(1, "a"), Node(2), Node(3, "A"), Node(4, "a")};
delete []q;
/*
Construct a 1
Construct 2
Construct A 3
Construct a 4
---------
Release a 4
Release A 3
Release 2
Release a 1
*/
delete 一个数组,而不加[]
只会释放第一个
Node *q = new Node[4]{Node(1, "a"), Node(2), Node(3, "A"), Node(4, "a")};
delete q;
/*
Construct a 1
Construct 2
Construct A 3
Construct a 4
---------
Release a 1
*/
智能指针和动态数组
智能指针保存动态数组,如果T是Node,记得传入自定义删除器;如果是Node[],其默认可以帮助删除(因为智能指针默认调用delete而没有delete[])
void delt(Node *n) {
delete[]n;
}
int main() {
{
cout << "unique_ptr<Node[]>" << endl;
unique_ptr<Node[]> p(new Node[2]);
}
{
cout << endl << "unique_ptr<Node>" << endl;
Node *n = new Node[2];
unique_ptr<Node, decltype(delt) *> pp(n, delt);
}
{
cout << endl << "hared_ptr<Node[]>:" << endl;
shared_ptr<Node[]> q(new Node[2]);
}
{
cout << endl << "shared_ptr<Node>" << endl;
Node *ptt = new Node[2]{Node(1, "A"), Node(2, "B")};
shared_ptr<Node> tt(ptt, [](Node *n) { delete[]n; });
}
cout << "------" << endl;
// {
// Node *pt = new Node[2]{Node(1, "A"), Node(2, "B")};
// shared_ptr<Node> t(pt);
// }
getchar();
}
/*
unique_ptr<Node[]>
Construct default 1
Construct default 1
Release default 1
Release default 1
unique_ptr<Node>
Construct default 1
Construct default 1
Release default 1
Release default 1
hared_ptr<Node[]>:
Construct default 1
Construct default 1
Release default 1
Release default 1
shared_ptr<Node>
Construct A 1
Construct B 2
Release B 2
Release A 1
------
*/
allocator
new和delete的缺陷 new将分配内存和对象构造组合在了一起,delete将析构和内存释放组合在了一起。
当我们只是想要分配一片连续内存,按需构造对象时,需要将内存分配和对象构造相分离。
allocator类
定义在头文件memory内,它可以分配原始的未构造的内存,他是个模板类。
allocator分配未构造的内存
struct Node {
int val;
string s;
~Node() {
printf("Release %s %d\n", s.c_str(), val);
}
Node() : val(1), s("default") {
// cout << "Construct ";
// show();
}
Node(int v, string t) : val(v), s(t) {
// cout << "Construct ";
// show();
}
Node(int v) : val(v), s("default") {
// cout << "Construct ";
// show();
}
void show() {
printf("%s %d\n", s.c_str(), val);
}
};
int main() {
allocator<Node> alloc;
auto const p = alloc.allocate(3);
// q指向最后构造元素之后的位置
auto q = p;
// 可以调用类的对应的构造函数
alloc.construct(q++, 0, "First");
alloc.construct(q++);
alloc.construct(q++, 2);
// error : 访问了未初始化的内存!!!
// q->show();
// 可以遍历之前已分配的内存
for (auto i = p; i != q; i++) {
i->show();
}
// 使用完对象后,需要对每个构造的对象调用析构函数来销毁他们
while (q != p) {
alloc.destroy(--q);
}
// 释放内存,注意这里释放的内存大小必须和分配时的一样大!
alloc.deallocate(p, 3);
}
/*
First 0
default 1
default 2
Release default 2
Release default 1
Release First 0
*/
拷贝和填充未初始化内存的算法
在未初始化的内存中创建对象
例如:vector扩容,给剩余值赋值-1
int main() {
vector<int> vi{1, 2, 3, 4, 5};
allocator<int> alloc;
auto const p = alloc.allocate(2 * vi.size());
auto q = uninitialized_copy(vi.begin(), vi.end(), p);
q = uninitialized_fill_n(q, vi.size(), -1);
while (q != p){
cout<<*(--q)<<" ";
}
}
// -1 -1 -1 -1 -1 5 4 3 2 1