C++Primer学习——动态内存

时间:2023-12-20 19:05:44

静态内存:用来保存static 栈内存:保存非static

智能指针:

shared_ptr:允许多个指针指向一个对象

unique_ptr:独占所指对象

weak_ptr:一种弱引用,指向shared_ptr的对象

shared_ptr:

shared_ptr<T> p;
p.get(); //返回p中保存的指针
p.unique(); //指针数量为1返回
p.use_count(); //返回共享指针数量
shared_ptr<string > p = make_shared_ptr<string>(10,'9');
shared_ptr<int> p = make_share_ptr<int>();
auto p = make_share_ptr<int>();

拷贝会使shared_ptr的计数器增加.初始化,函数参数或者函数返回值。

一旦计数器为0时就会通过析构函数释放自己

函数结束会返回一个p的拷贝,增加计数器,所以函数结束含有指针指向p的内存,所以不会被销毁

void use_factory(T arg)
{
shared_ptr<Foo> p = factory(arg);
return p;
}

所以如果你将shared_ptr放在一个容器中,而后不再需要全部的元素,记得把不用元素删除。

使用动态生存周期的资源:

1.程序不知道自己有多少的对象 //容器类

2.程序不知道自己做需要对象的准确类型

3.程序需要在多个对象间共享数据 //通过shared_ptr的计数器特性实现

直接管理内存:

通过new和delete来管理内存,与智能指针不同的是,直接管理内存无法依赖类对象拷贝、赋值和销毁操作的任何默认定义。

默认初始化和值初始化:

对于定义了构造函数的类类型来说两者都会调用构造函数初始化,没意义;但对于内置类型,值初始化有良好定义的值,而默认初始化

的值是未定义的。

int *p = new int;       //默认初始化,值未定义
int *p = new int(); //值初始化为0,*p为0

所以在定义const对象时,如果有构造函数的类类型其const对象可以隐式初始化,但是其他类型就必须显示初始化了。

const int *pci = new const int(1024);
const string *s = new const string;

如果提供一个初始化器,就能使用auto判断我们想要的类型,但是只有当单一的初始化器时才能使用

auto p = new auto(obj);
auto p = new auto(a,b,c); //error

释放一块并非new分配的内存或者多次释放相同指针是未定义的行为。

动态对象的生命周期到被释放为止:

当离开作用域时,局部的指针被销毁了,但是所指内存并没有释放

void use_factory(T arg)
{
Foo *p = factory(arg);
//使用p但不delete
//即便离开作用于,所指内存没被释放
}

常见问题:忘记delete、使用已经释放了的内存,同一块内存释放了两次

shared_ptr和new的结合使用:

接受指针参数的智能指针的构造函数是explicit,new会返回一个指向该对象的指针

shared_ptr<int>p1 = new int(1024);       //error 需要隐式转换成shared_ptr
shared_ptr<int>p2(new int(1024));
shared_ptr<int> clone(int p)
{
return new int(p); //error 需要隐式转换成shared_ptr
return shared_ptr<int>(new int(p));
}

默认情况下初始化智能指针的普通指针必需指向动态内存,因为智能指针默认使用delete释放。

否则需要自定义操作来替代delete

shared_ptr<T> p(q); //p管理q所指向的对象
shared_ptr<T> p(u); //p接管unique_ptr并将u置为空
shared_ptr<T> p(q,d); //p接管q,用d代替delete
shared_ptr<T> p(p2,d); //p2的一个拷贝
p.reset();
p.reset(q);
p.reset(q,d);

不要混用普通指针和智能指针:

当智能指针和普通指针指向了同一个内存,如果智能内存的计数减为0,那么内存会被释放

所以当同时用的时候,将内存管理交给shared_ptr

int *x = &q;
process(shared_ptr<int>(x) ); //只能指针会被销毁,内存释放
int j = *x; //空悬指针

可以通过get()向不能使用智能指针的传递一个内置指针,但get返回的指针不能delete

get最好用来传递访问权限,不能用get初始化另外一个智能指针或者为另一个智能指针赋值

无法将普通指针赋值给shared_ptr,所以需要reset

p = new int(42); //error 不能隐式转换?

p.reset(new int(42)); //correct

对于异常,智能指针无论怎样都能确保内存释放,但是直接管理内存时,如果在delete之前异常弹出,那么直接管理的内存

并不会释放。

如果类没有析构函数而且没有显示的关闭连接,那么会出现资源泄露。

可以考虑将其与一个shared_ptr绑定,然后指定delete该指针的方法,从而保证正常关闭

void end_connection(connection *p) {disconnect(p);}
void f(destination &d)
{
connection c = connect(&d);
shared_ptr<connection> p(&c,end_connection);
}

智能指针error:

1.不使用相同的内置指针(或者reset)初始化多个智能指针。 //计数器全是1?

shared_ptrp1(p);

shared_ptrp2(p);

cout << p1.use_count() << " " <<p2.use_count() <<endl;

//1 1

2.不delete get()返回的指针

3.不适用get初始化或者reset另一个智能指针

4.注意get()返回的指针会在智能指针销毁后失效

5.如果你管理的不是new分配的内存,指定一个删除器

unique_ptr:

没有类似make_shared的函数,只能绑定到一个new返回的指针上面。同样只能直接初始化

unique_ptr<string> p1(new string("123"));
unique_ptr<string> p2(p1.release());
//转移控制权,release放弃控制并返回指针
unique_ptr<string> p3(new string("abc"));
p2.reset(p3.release());
而且可以拷贝一个将要被销毁的unique_ptr,因为编译器知道它将被销毁所以执行一种特殊的拷贝
unique_ptr<int> clone(int p)
{
return unique_ptr<int>(new int(p));
}

unique_ptr可以自定义删除器:

connection c = connect(&d);
unique_ptr<connection , decltype(end_connection)*>
p(&c,end_connection);

weak_ptr:

是一种不控制所指对象生存期的只能指针,指向一个shared_ptr对象,但不会改变引用计数。

w.use_count();  //返回shared_ptr数量
w.expired(); //use_count为0返回true
w.lock(); //如果expired()为true返回一个空指针,否则返回一个shared_ptr

动态数组:

vector和string都是连续内存中保存元素,所以每次要分配大量内存。

可以进行默认初始化或者值初始化,由于不能在()中给出初始化器,意味着不能auto分配数组

int *p = new int[10];         //10个未初始化int
int *p = new int[10](); //10初始化为0的int
int *p = new int[10]{0,1,2,3,4}; //前几个用给定初始化器初始化
delete []p;
delete时[]告诉编译器指针指向对象数组的第一个元素,如果不加[]是未定义的行为。而且释放内存时逆序销毁

1.与unique_ptr:

unique_ptr<int[]> p(new int[10]);
p.release(); //自动调用delete

2.与shared_ptr:

和unique不同的是,shared不支持自动管理动态数组QAQ,所以需要定义删除器

如果没有删除器,那么产生问题和delete没加[]一样(即默认调用delete p)

shared_ptr<int> p(new int[10],[](int *p){delete[] p;});
p.reset(); //通过lambda表达式释放

shared_ptr没有下标运算符,而且只能指针不支持指针算术运算,所以访问数组中的元素。

必需要get()获取内置指针

*(p.get() + i) = i;

allocator

在用new分配的时候,不知道数量所以可能把数组分配过大。 而且每个使用的元素都要赋值两次,默认初始化 + 赋值。

如果没有默认构造函数,那么不能动态分配数组。

allocator<string> alloc;
auto p = alloc.allocate(5); //分配5个未构造原始内存
auto q = p;
alloc.construct(q++,"Orz");
cout << *p <<endl;
alloc.destroy(--q);
alloc.deallocate(p,5);
destory() 释放对象内的动态内存(如果有) deallocate是释放对象本身占有的内存

所以要先destory再进行deallocate

unintialized_copy(b,e,b2):从迭代器b和e指出的输入范围中拷贝元素到迭代器b2指定的未构造的原始内存中。b2指向的内存必须足够大,能容纳输入序列中元素的拷贝
uninitialized_copy_n(b,n,b2):从迭代器b指向的元素开始,拷贝n个元素到b2开始的内存
unintialized_fill(b,e,b2):在[b,e]创建b2的拷贝
uninitialized_fill_n(b,n,b2):在b开始创建b个 pair<string*,string*>
alloc_n_copy(const string* b,const string* e)
{
auto data = alloc.allocate(e-b);
return {data,uninitialized_copy(b,e,data)};
}