动态内存C++
Posted 扣得君
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了动态内存C++相关的知识,希望对你有一定的参考价值。
第12章 动态内存
动态内存的动态体现在哪里,还要从我们前面所知道的全局变量,局部变量,以及static变量进行对比,全局变量的生命周期横穿整个程序运行时知道程序运行结束,局部变量随着调用栈上下文的离开进行释放,static初始化在第一次被运行时变量被定义,知道程序运行结束才释放,而动态内存就是编码人员手动显式的申请的内存,只有显式地进行释放才会被释放,否则知道程序运行结束
C语言也有动态内存,而其也是一件非常危险地是事情,经验不足的开发人员可能编码造成内存泄露。在C++中,标准库定义了两个智能指针类型来管理动态分配的对象,当一个对象应该被释放时,指向它的智能指针可以确保自动地释放它
两种智能指针
新的标准库提供两种智能指针类型来管理动态对象,智能指针与普通指针类似,但区别在于它负责自动释放所指向的对象
两种指针的区别之处在于管理底层指针的方式:shared_ptr允许多个指针指向同一个对象;unique_ptr则“独占”所指对象,不能再有其他指针指向其指的对象
标准库还定义了weak_ptr的伴随类,是一种弱引用,指向shared_ptr所管理的对象,它们都在头文件memory
中
shared_ptr和unique_ptr都支持的操作
//example1.cpp
shared_ptr<string> str_ptr; //可以指向string
shared_ptr<vector<int>> vec_ptr; //可以指向vector<int>
//判断指针是否为空
if (str_ptr != nullptr)
*str_ptr = "hello"; //解引用
shared_ptr独有的操作
make_shared函数
make_shared<T>(args)函数就是申请可以由shared_ptr管理的内存,args为T的构造函数参数
//example2.cpp
shared_ptr<string> str_ptr = make_shared<string>("hello");
shared_ptr<string> str_ptr1 = make_shared<string>("world");
if (str_ptr)
cout << *str_ptr << endl; // hello
// get()方法获得普通指针
string *ptr = str_ptr.get();
cout << *ptr << endl; // hello
//交换指针
str_ptr1.swap(str_ptr);
cout << *str_ptr << " " << *str_ptr1 << endl; // world hello
//使用swap函数
swap(str_ptr, str_ptr1);
cout << *str_ptr << " " << *str_ptr1 << endl; // hello world
shared_ptr的拷贝和赋值
shared_ptr在拷贝或赋值时,每个shared_ptr都会记录有多少个其他shared_ptr指向相同的对象
shared_ptr采用引用计数,当我们拷贝一个shared_ptr,计数器就会加一,当shared_ptr被赋予新的值时或者shared_ptr离开作用域,则会将计数器减一
一旦一个shared_ptr的计数器变为0,他就会释放自己所管理的对象的内存
//example3.cpp
void m_function()
shared_ptr<string> str_ptr = make_shared<string>("dynamic memory");
//当上下文离开function时 栈内存变量 str_ptr则会被释放销毁 则那块内存的引用计数变为0
//也会被释放掉
int main(int argc, char **argv)
//make_shared申请的内存,内存的引用数量为0
//str_ptr指向申请的内存,内存的引用数量加1
shared_ptr<string> str_ptr = make_shared<string>("hello");
//又有一个新的shared_ptr指向那块内存,则引用计数+1
shared_ptr<string> str_ptr1 = str_ptr;
str_ptr1 = nullptr; //引用计数减一
str_ptr = nullptr; //引用计数变为0 那块内存的引用计数变为0 则进行释放
m_function();
cout << "over" << endl; // over
return 0;
shared_ptr 自动销毁所管理的对象
因为shared_ptr被销毁时会先执行其析构函数,析构函数内判断所指向的对象的引用计数,如果只剩自己本身指向它,则会将对象释放掉
更加细节的事情
//example4.cpp
shared_ptr<string> m_function()
shared_ptr<string> str_ptr = make_shared<string>("dynamic memory");
return str_ptr;
//首先make_shared申请的内存引用数量为0
// str_ptr指向其对象 则引用数量变为1
//后面return 即进行了拷贝str_ptr存储到临时变量 引用数量边为2
//上下文离开str_ptr销毁 引用数量变为1
//如果调用者接收了返回的参数 则引用数量变为2
//临时变量被销毁 引用数量变为1
//如果调用者没有接收返回的参数,则临时变量被销毁,引用数量变为0,对象内存内释放
int main(int argc, char **argv)
m_function(); //内部申请的内存被释放
shared_ptr<string> str_ptr = m_function(); //内部申请的内存不会被释放,因为有str_ptr指向
cout << *str_ptr << endl; // dynamic memory
return 0;
我们会发现,合理利用智能指针C++也可以像Java一样拥有优秀的内存管理,而且是直接操纵内存级别
为什么要用动态内存
程序使用动态内存出于一下三种原因之一
- 程序不知道自己需要使用多少对象
- 程序不知道所需对象的准确类型
- 程序需要在多个对象间共享数据
有趣典型的多个对象共享一个对象的例子
//example5.cpp
class Person
public:
shared_ptr<string> name;
Person() = default;
Person(const Person &person)
name = person.name;
;
int main(int argc, char **argv)
Person person;
//内部作用域
Person person1;
person1.name = make_shared<string>("gaowanlu");
person = person1;
cout << *person.name << endl; // gaowanlu
*person.name = "hi";
cout << *person1.name << endl; // hi
cout << *person.name << endl; // hi
//作用域离开person1被销毁,但申请的内存仍存在引用计数
cout << *person.name << endl; // hi
return 0;
直接管理内存
学过C语言的话可以知道在stdlib头文件中,有malloc函数与realloc函数
//example6.cpp
int *int_arr = (int *)malloc(sizeof(int) * 10);
for (int i = 0; i < 10; i++)
int_arr[i] = i;
int_arr = (int *)realloc(int_arr, sizeof(int) * 20);
for (int i = 10; i < 20; i++)
int_arr[i] = i;
for (int i = 0; i < 20; i++)
cout << int_arr[i] << " ";
// 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
cout << endl;
free(int_arr);
在C++中定义了两个运算符来显式地配分和释放内存,运算符new用于分配内存,delete释放new分配的内存
使用new动态分配和初始化对象
Type* ptr=new Type(args)
动态分配内存
1、new基本类型与自定义数据类型,总之使用构造函数
//example7.cpp
int *p1 = new int; //未初始化的int
*p1 = 999;
cout << *p1 << endl; // 999
int *p2 = new int(1); //初始化为1
cout << *p2 << endl; // 1
string *p3 = new string; //初始化为空的string
string *p4 = new string(); //初始化为空的string
string *p5 = new string("hello");
string *p6 = new string(10, 'p'); //初始化为10个p的字符串
2、new顺序容器
//不仅仅可以new基本数据类型
vector<int> *p7 = new vector<int>1, 2, 3, 4, 5;
for (auto &item : *p7)
cout << item << " "; // 1 2 3 4 5
cout << endl;
3、auto接收指针
//使用auto
auto p8 = new string("3232"); // p8的类型是通过"3232"类型推断出来的
auto p9 = new vector<int>1, 2, 3, 4, 5;
4、auto推断要new的数据类型
//更厉害的auto用法,尽量不要用,C++可是非弱类型语言
auto p10 = new auto(1); //根据1的类型自动推断
// auto p11 = new auto1, 2, 3, 4;//括号只能有单个初始化器
auto p12 = new autostring("1"); //括号单个初始化 std::initializer_list<string>
auto str = (*p12).begin();
cout << *str << endl; // 1
// delete p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p12;//错误 写法 delete优先级比,高
delete p1, delete p2, delete p3, delete p4, delete p5, delete p6, delete p7, delete p8, delete p9, delete p10, delete p12;
动态分配的const对象
使用new分配const对象是被允许的
//example8.cpp
const int *int_ptr = new const int(666); //必须被初始化
//*int_ptr = 999;// error: assignment of read-only location '* int_ptr'
cout << *int_ptr << endl; // 666
const string *str_ptr = new const string(10, 'p');
cout << *str_ptr << endl; // pppppppppp
delete int_ptr,delete str_ptr;
内存耗尽
我们知道计算机的内存是有限的,操作系统对某个进程可能也存在内存的大小限制,在显式动态分配内存时,可能会分配失败,当分配失败,有两种选择
1、new Type(args)
抛出std::bad_alloc异常
2、在使用new时new (nothrow) Type(args)
形式进行,则分配失败时返回空指针
bad_alloc和nothrow都定义在头文件new中
//example9.cpp
int *p1 = new (nothrow) int; //分配失败异常 返回空指针
if (p1 == nullptr)
assert("内存分配失败");
else
cout << "内存分配成功" << endl; //内存分配成功
delete p1;
p1 = nullptr;
try
p1 = new int; //分配失败时则抛出异常
catch (std::bad_alloc e)
cout << e.what() << endl;
if (p1)
delete p1;
p1 = nullptr;
释放动态内存
delete表达式用来将动态内存归还给系统 delete ptr;
ptr必须指向一个动态分配的对象或一个空指针
delete执行两个动作,如果对象有析构函数则会执行析构函数销毁对象,然后释放对应的内存
指针值和delete
重要的是,delete释放的内存必须是我们申请的动态内存,栈内存可不能手动释放
//example10.cpp
int *num = new int(99);
cout << *num << endl; // 99
delete num;
delete num; //未定义 因为num指向的内存已经被释放
cout << *num << endl; // 17211320
int *ptr = nullptr;
delete ptr; //释放一个空指针没有错误
int stack_num = 100;
delete &stack_num; //未定义 因为&statck_num为栈内存
cout << stack_num << endl; // 100
const int *const_ptr = new const int(99);
delete const_ptr;
动态对象的生存周期
什么是内存泄露,简单地说就是我们申请了内存,我们都是动过其内存地址进行访问的,但如果内存没有释放,但是我们无法获取其地址了,那么内存就会白白被占着,知道程序停止运行
容易出现的错误
//example11.cpp
int *p = new int;
p = nullptr; //内存泄露 再也找不回那块内存的地址
//被释放后又使用
p = new int;
int *p1 = p;
delete p1;
*p = 999;
//卡住因为二者指向同一块内存但是已经被释放过了
//不能在被使用
所以尽量在delete之后,将指针指向nullptr即空指针
int *p=new int;
delete p;
p=nullptr;
shared_ptr和new结合使用
如果不初始化一个shared_ptr则将会是一个空指针,除了make_shared还以使用以下其他方法定义和改变shared_ptr
//example12.cpp
//返回返回
shared_ptr<int> func()
// return new int(1);//错误:因为shared_ptr的构造函数是explicit的
return shared_ptr<int>(new int(1)); //正确
int main(int argc, char **argv)
int *p = new int(999);
shared_ptr<int> ptr1(p); //接管p所指向的对象
shared_ptr<int> ptr2(new int(1));
shared_ptr<int> ptr3 = func();
cout << *ptr3 << endl; // 1
return 0;
不要混合使用普通指针和智能指针
由shared_ptr接管的new出来的内存,如果share_ptr自动释放了它,但我们又使用普通指针使用它,则会出现错误
//example13.cpp
void func(shared_ptr<int> ptr)
// use ptr
int main(int argc, char **argv)
shared_ptr<int> ptr1 = make_shared<int>(999);
func(ptr1); //地址进行值传递
cout << *ptr1 << endl; // 999
int *ptr2(new int(666));
func(shared_ptr<int>(ptr2));
cout << *ptr2 << endl; // 17080272 换码 可见new出来的对象被释放掉了
//ptr2变成为悬空指针
return 0;
为什么会这样呢,因为shared_ptr是按照值传递的,在func(shared_ptr<int>)(ptr2)
时引用数为1,当赋值给func的形参后引用数为2,然后传参临时变量销毁引用数变为1,随着func运行结束形参ptr被销毁,引用数变为0,内存也会被释放
不要使用get初始化另一个智能指针或智能指针赋值
确定shared_ptr.get()出来的指针不会被delete再使用get,永远不要用get初始化另一个智能指针或另一个智能指针赋值
//example14.cpp
int main(int argc, char **argv)
shared_ptr<int> ptr1 = make_shared<int>(999);
int *ptr2 = ptr1.get(); //获取对象内存的普通地址
shared_ptr<int>(ptr2);
//因为在构造函数中 ptr2只是被认为是new出来的普通的内存地址
//不知道是已经被shared_ptr接管的
//随着作用域消失ptr3被销毁 内存也会被释放
*ptr2 = 888;
cout << *ptr2 << endl;
cout << *ptr1 << endl;
//但有些编译器这些操作是没错误的
return 0;
其他shared_ptr操作
常用的有reset、unique方法,shared_ptr的reset方法用于shared_ptr去掉对其指向对象的引用,如果reset后对象内存引用数为0则对象内存会被释放,unique方法用于检测shared_ptr指向的对象的内存引用数是否为1,为1则返回true否则反之。
//example15.cpp
int main(int argc, char **argv)
shared_ptr<int> ptr1 = make_shared<int>(888);
shared_ptr<int> ptr2 = ptr1;
//指向对象内存引用数是否为1
cout << ptr1.unique() << endl; // 0
ptr1.reset();
cout << *ptr2 << endl;
*ptr2 = 999;
cout << *ptr2 << endl; // 999
//可见reset就是将shared_ptr对指向对象内存引用数减1
//同时如果引用数变为0也会被释放
cout << ptr2.unique() << endl; // 1
shared_ptr<int> ptr3(new int(999));
int *ptr4 = ptr3.get();
ptr3.reset();
cout << *ptr4 << endl; //指针悬空
return 0;
智能指针和异常
最常见的情况
//example16.cpp
void func()
int *p = new int(111);
throw std::logic_error("logic_error"); //抛出异常 导致delete无法被执行
//进而造成内存泄露
delete p;
int main(int argc, char **argv)
try
func();
catch (std::logic_error e)
cout << e.what() << endl; // logic_error
cout << "over" << endl; // over
return 0;
当自定义类没有析构函数时,但是在其内部存储了new的内存的地址的指针,但是在析构函数时并没有对其进行释放操作,在使用shared_ptr管理时,当对象内存释放,内部指针指向的内存无法被释放
//example17.cpp
class Person
public:
string *ptr;
explicit Person()
ptr = new string("");
~Person()
cout << "release" << endl;
// delete ptr;
;
void func()
shared_ptr<Person> ptr = make_shared<Person>C++ 动态内存