Posts C++primer-p12 动态内存
Post
Cancel

C++primer-p12 动态内存

第十二章 动态内存

  • 对象的生命周期:
    • 全局对象在程序启动时分配,结束时销毁。
    • 局部对象在进入程序块时创建,离开块时销毁。
    • 局部static对象在第一次使用前分配,在程序结束时销毁。
    • 动态分配对象:只能显式地被释放。
  • 对象的内存位置:
    • 静态内存用来保存局部static对象、类static对象、定义在任何函数之外的变量。
    • 栈内存用来保存定义在函数内的非static对象。
    • 堆内存,又称自由空间,用来存储动态分配的对象。

动态内存与智能指针

  • 动态内存管理:
    • new:在动态内存中为对象分配空间并返回一个指向该对象的指针。
    • delete:接受一个动态对象的指针,销毁该对象,并释放与之关联的内存。
  • 智能指针:
    • 管理动态对象。
    • 行为类似常规指针。
    • 负责自动释放所指向的对象。
    • 智能指针也是模板。

shared_ptr类

shared_ptr和unique_ptr都支持的操作

操作解释
shared_ptr<T> sp unique_ptr<T> up空智能指针,可以指向类型是T的对象
pp用作一个条件判断,若p指向一个对象,则为true
*p解引用p,获得它指向的对象。
p->mem等价于(*p).mem
p.get()返回p中保存的指针,要小心使用,若智能指针释放了对象,返回的指针所指向的对象也就消失了。
swap(p, q) p.swap(q)交换pq中的指针

shared_ptr独有的操作

操作解释
make_shared<T>(args)返回一个shared_ptr,指向一个动态分配的类型为T的对象。使用args初始化此对象。
shared_ptr<T>p(q)pshared_ptr q的拷贝;此操作会递增q中的计数器。q中的指针必须能转换为T*
p = qpq都是shared_ptr,所保存的指针必须能互相转换。此操作会递减p的引用计数,递增q的引用计数;若p的引用计数变为0,则将其管理的原内存释放。
p.unique()p.use_count()是1,返回true;否则返回false
p.use_count()返回与p共享对象的智能指针数量;可能很慢,主要用于调试。

智能指针问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Str{
public:
    using size_type = vector<string>::size_type;
    Str() ;
    Str(initializer_list<string> li);
public:
    shared_ptr<vector<string>> data;
    void check(size_type i,const string &msg) const;
};
Str::Str(initializer_list<string> li) :data(make_shared<vector<string>>(li)){};
Str::Str() :data(make_shared<vector<string>>()){};     // 初始化一个空的 shared_ptr<> 指针

void test04(){
    Str str1{"hello","jay"};
    cout << str1.data << endl;    // 0x600002cd8228
    Str str2 = str1;
    shared_ptr<vector<string>> str3;
//    str3 = str2.data;
    cout << str2.data << endl;    // 0x600003850228
    cout << str1.data.unique() << endl;    // 0
    cout << str1.data.use_count() << endl;   //2
}

initializer_list 初始化问题

1
2
3
4
5
6
7
8
void method(initializer_list<string> li){
    vector<string> vec(li.begin(),li.end());
    for (const auto &item : vec)
        cout << item << endl;
}
void test03(){
    method({"hello","jay","mimi"});
}
  • 使用动态内存的三种原因
    • 程序不知道自己需要使用多少对象(比如容器类)。
    • 程序不知道所需要对象的准确类型。
    • 程序需要在多个对象间共享数据。

直接管理内存

  • new动态分配和初始化对象。
    • new无法为分配的对象命名(因为自由空间分配的内存是无名的),因此是返回一个指向该对象的指针。
    • int *pi = new int(123);
    • 一旦内存耗尽,会抛出类型是bad_alloc的异常。
  • delete将动态内存归还给系统。
    • 接受一个指针,指向要释放的对象。
    • delete后的指针称为空悬指针(dangling pointer)。
  • 使用newdelete管理动态内存存在三个常见问题:
    • 1.忘记delete内存。
    • 2.使用已经释放掉的对象。
    • 3.同一块内存释放两次。
  • 坚持只使用智能指针可以避免上述所有问题。

shared_ptr和new结合使用

定义和改变shared_ptr的其他方法

操作解释
shared_ptr<T> p(q)p管理内置指针q所指向的对象;q必须指向new分配的内存,且能够转换为T*类型
shared_ptr<T> p(u)punique_ptr u那里接管了对象的所有权;将u置为空
shared_ptr<T> p(q, d)p接管了内置指针q所指向的对象的所有权。q必须能转换为T*类型。p将使用可调用对象d来代替delete
shared_ptr<T> p(p2, d)pshared_ptr p2的拷贝,唯一的区别是p将可调用对象d来代替delete
p.reset()p是唯一指向其对象的shared_ptrreset会释放此对象。若传递了可选的参数内置指针q,会令p指向q,否则会将p置空。若还传递了参数d,则会调用d而不是delete来释放q
p.reset(q)同上
p.reset(q, d)同上

智能指针与new相结合使用

1
2
3
4
5
6
7
8
9
10
11
void test05(){
    shared_ptr<int> p1(new int(1024));
    int *p2 = new int(10);
    shared_ptr<int> p3(p2);
    cout << *p3 << endl;

    // 管理指针
    User *u = new User(29);
    shared_ptr<User> user(u);
    cout << user->age << endl;
}

不要混用普通指针与智能指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void method1(shared_ptr<string> ptr){
    cout << *ptr << endl;
    cout << ptr.use_count() << endl;   // 2
    return;
}
void test07(){
    shared_ptr<string> p(new string("hello jay"));
    cout << p.use_count() << endl;  // 1
    method1(p);
    cout << p.use_count() << endl;    // 1


//    错误示范
    string *str = new string("hello");
//    method1(str);  // 错误
    method1(shared_ptr<string>(str));   // 正确的 但是内存会被释放掉
    cout << *str << endl;   // 乱码 内存被释放
}

将指针交给智能指针管理后,就不应该再使用内置指针来访问。

不要使用get初始化另外一个指针指针

1
2
3
4
5
6
7
8
9
shared_ptr<int> p(new int(42));
int *q = p.get();
{
  // 新程序块
  shared_ptr<int> (q);
  // 程序块结束 q被销毁、
}
int foo = *p;  // 未定义

使用make_shared

1
2
3
4
5
void test10(){
    shared_ptr<string> p = make_shared<string>("string");
    cout << p << endl;
    cout << *p << endl;
}
1
2
3
4
5
6
void test11(){
    string *str = new string("hello jay");
    shared_ptr<string> p(str);
    cout << str << endl;
    cout << p << endl;
}

reset操作

1
2
3
4
if(!p.unique())
  p.reset(new string(*p));     // 我们不是唯一用户,重新分配拷贝

*p += newVal;   // 现在我们是唯一用户了 可以改变对象的值

智能指针和异常

  • 如果使用智能指针,即使程序块由于异常过早结束,智能指针类也能确保在内存不需要的时候将其释放。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    
    void f(){
      shared_ptr<int> sp(new int(42));
      // 发生异常 而且未被捕获
    } // 函数结束时, shared_ptr 自动释放内存
    
    
    void f(){
      int *ip = new int(42);
      // 此时发生异常 且未被捕获
      delete p;  // 内存不会被释放,异常时
    }
    
    
  • 智能指针陷阱

    • 不用相同的内置指针初始化(或reset)多个智能指针
    • delete get()返回的指针。
    • 如果你使用get()返回的指针,记得当最后一个对应的智能指针销毁后,你的指针就无效了。
    • 如果你使用智能指针管理的资源不是new分配的内存,记住传递给它一个删除器。

unique_ptr

  • 某一个时刻只能有一个unique_ptr指向一个给定的对象。
  • 不支持拷贝或者赋值操作
  • 向后兼容:auto_ptr:老版本,具有unique_ptr的部分特性。特别是,不能在容器中保存auto_ptr,也不能从函数返回auto_ptr
  • 虽然不支持拷贝与赋值操作,但是我们可以拷贝或者赋值一个将要被销毁的unique_ptr
    1
    2
    3
    4
    
    unique_ptr<int> clone(int p){
    
      return unique_ptr<int> (new int(p));
    }
    

unique_ptr操作:

操作解释
unique_ptr<T> u1unique_ptr,可以指向类型是T的对象。u1会使用delete来是释放它的指针。
unique_ptr<T, D> u2u2会使用一个类型为D的可调用对象来释放它的指针。
unique_ptr<T, D> u(d)unique_ptr,指向类型为T的对象,用类型为D的对象d代替delete
u = nullptr释放u指向的对象,将u置为空。
u.release()u放弃对指针的控制权,返回指针,并将u置空。
u.reset()释放u指向的对象
u.reset(q)u指向q指向的对象
u.reset(nullptr)u置空
1
2
3
4
5
6
7
8
void test12(){
    unique_ptr<string> p(new string("hello jay"));
    cout << *p << endl;
    unique_ptr<string> p2 ;
//    p2 = p;  // 错误 unique 为唯一指针
    p2.reset(p.release());
    cout << *p2 << endl;
}
1
2
3
4
5
6
7
// 删除问题
void test13(){
    unique_ptr<string> p(new string("hello jay"));
    p.release();   // 错误  p不会被释放内存
    auto p2 = p.release();  // p的内存被释放  转为p2  
    delete(p2);   // 释放p2 
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 拷贝问题  原则上unique_ptr 是不允许拷贝赋值的
//1.
unique_ptr<string> method3(string str){
    return unique_ptr<string> (new string(str));
}
//2. 还可以返回一个局部对象的拷贝
unique_ptr<string> method4(string str){
    unique_ptr<string> p(new string(str));
    return p;
}
void test14(){
    auto p = method3("hello");
    auto p2 = method4("hello");
    cout << *p << endl;
    cout << *p2 << endl;
}

传递销毁器与shared_ptr类似,但又不相同

weak_ptr

  • weak_ptr是一种不控制所指向对象生存期的智能指针。
  • 指向一个由shared_ptr管理的对象,不改变shared_ptr的引用计数。
  • 一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放,不管有没有weak_ptr指向该对象。

weak_ptr操作:

操作解释
weak_ptr<T> wweak_ptr可以指向类型为T的对象
weak_ptr<T> w(sp)shared_ptr指向相同对象的weak_ptrT必须能转换为sp指向的类型。
w = pp可以是shared_ptr或一个weak_ptr。赋值后wp共享对象。
w.reset()w置为空。
w.use_count()w共享对象的shared_ptr的数量。
w.expired()w.use_count()为0,返回true,否则返回false
w.lock()如果expiredtrue,则返回一个空shared_ptr;否则返回一个指向w的对象的shared_ptr
1
2
3
4
5
6
7
void test15(){
    shared_ptr<string> p(new string("hello jay"));
    weak_ptr<string> p1(p);
    cout << p1.use_count() << endl;
    auto p3 = p1.lock();
    cout << p3.use_count() << endl;
}
1
2
3
4
5
6
7
8
9
void test15(){
    shared_ptr<string> p(new string("hello jay"));
    weak_ptr<string> p1(p);
//    cout << *p1 << endl;   // 因为p1 可能不存在 所以不可以直接访问p1
    if(auto p2 = p1.lock()){     // 如果p1不存在,直接返回false 不会执行下列语句
        cout << *p2 << endl;
        cout << p2.use_count() << endl;
    }
}

动态数组

new和数组

  • new一个动态数组:
    • 类型名之后加一对方括号,指明分配的对象数目(必须是整型,不必是常量)。
    • 返回指向第一个对象的指针
    • int *p = new int[size];
  • delete一个动态数组:
    • delete [] p;
  • unique_ptr和数组:
    • 指向数组的unique_ptr不支持成员访问运算符(点和箭头)。
操作解释
unique_ptr<T[]> uu可以指向一个动态分配的数组,整数元素类型为T
unique_ptr<T[]> u(p)u指向内置指针p所指向的动态分配的数组。p必须能转换为类型T*
u[i]返回u拥有的数组中位置i处的对象。u必须指向一个数组。

allocator类

  • 标准库allocator类定义在头文件memory中,帮助我们将内存分配和对象构造分离开。
  • 分配的是原始的、未构造的内存。
  • allocator是一个模板。
  • allocator<string> alloc;

标准库allocator类及其算法

操作解释
allocator<T> a定义了一个名为aallocator对象,它可以为类型为T的对象分配内存
a.allocate(n)分配一段原始的、未构造的内存,保存n个类型为T的对象。
a.deallocate(p, n)释放从T*指针p中地址开始的内存,这块内存保存了n个类型为T的对象;p必须是一个先前由allocate返回的指针。且n必须是p创建时所要求的大小。在调用deallocate之前,用户必须对每个在这块内存中创建的对象调用destroy
a.construct(p, args)p必须是一个类型是T*的指针,指向一块原始内存;args被传递给类型为T的构造函数,用来在p指向的内存中构造一个对象。
a.destroy(p)pT*类型的指针,此算法对p指向的对象执行析构函数。

allocator伴随算法

操作解释
uninitialized_copy(b, e, b2)从迭代器be给定的输入范围中拷贝元素到迭代器b2指定的未构造的原始内存中。b2指向的内存必须足够大,能够容纳输入序列中元素的拷贝。
uninitialized_copy_n(b, n, b2)从迭代器b指向的元素开始,拷贝n个元素到b2开始的内存中。
uninitialized_fill(b, e, t)在迭代器be执行的原始内存范围中创建对象,对象的值均为t的拷贝。
uninitialized_fill_n(b, n, t)从迭代器b指向的内存地址开始创建n个对象。b必须指向足够大的未构造的原始内存,能够容纳给定数量的对象。
  • 定义在头文件memory中。
  • 在给定目的位置创建元素,而不是由系统分配内存给他们。
This post is licensed under CC BY 4.0 by the author.

Contents

Trending Tags