动态内存笔记

智能指针

为了更安全地使用动态内存(避免内存泄漏),引入了智能指针。

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,不然仍会存在内存泄漏。

使用动态生存期的资源的类

使用动态内存的原因:

  1. 程序不知道自己需要多少对象(如vector)
  2. 程序不知道对象的准确类型
  3. 程序需要在多个对象间共享数据
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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值