C++Primer 直接管理内存 new and delete

使用new动态分配和初始化对象
在自由空间分配的内存是无名的,因此new无法为其分配的对象命名,而是返回一个指向该对象的指针:

int *pi = new int;  //pi指向一个动态的、未初始化的无名对象

此new表达式在自由空间构造一个int类型的对象,并返回指向该对象的指针
默认情况下,动态分配的对象是默认初始化的,这意味着内置类型或组合类型的对象的值是未定义的,而类类型对象将用默认构造函数进行初始化:

string *ps = new string;   //初始化为空string
int *pi = new int;       //pi指向一个未初始化的int

我们可以使用传统的构造方式,用圆括号,或者用列表初始化

int *pi = new int(1024);     //p指向的对象的值是1024
vector<int> *pv = new vector<int>{0,1,2,3,4,5,6,7,8,9};   //vector有10个元素,值依次为0到9

也可以对动态分配的对象进行值初始化,只需在类型名后跟一对空括号即可

string *ps1 = new string;   //默认初始化为空string
string *ps = new string();   //值初始化为空string
int *pi1 = new int;         //默认初始化,*pi1的值是未定义的
int *pi2 = new int();       //值初始化为0,*pi2的值为0

对于定义了自己的构造函数的类类型(例如string)来说,要求值初始化是没有意义的;不管采用什么形式,对象都会通过默认构造函数来初始化。
对于内置类型,值初始化的内置类型对象有着良好定义的值,而默认初始化的对象的值则是未定义的。对于类中那些依赖于编译器合成的默认构造函数的内置类型成员,如果它们未在类内被初始化,那么它们的值也是未定义的。

动态分配的const对象
用new分配const对象是合法的:

//分配并初始化一个const int
const int *pci = new const int(1024);
//分配并默认初始化一个const的空string
const string *pcs = new const string;

类似其他任何const对象,一个动态分配的const对象必须进行初始化。对于一个定义了一个默认构造函数的类类型,其const动态对象可以隐式初始化,而其他类型的对象就必须显式初始化。由于分配的对象是const的,new返回的指针也是一个指向const的指针

内存耗尽
如果new不能分配所需要的内存空间,它会抛出一个类型为bad_alloc的异常,我们可以改变使用new的方式来阻止它抛出异常

bad_alloc和nothrow都定义在头文件new中

int *p1 = new int;   //如果分配失败,new抛出std::bad_alloc
int *p2 = new (nothrow) int;   //如果分配失败,new返回一个空指针

释放动态内存
delete表达式接受一个指针,指向我们想要释放的对象:
传递给delete的指针必须指向动态分配的内存,或者是一个空指针。释放一块并非new分配的内存,或者将相同的指针值释放多次,其值都是未定义的。
虽然一个const对象的值不能被改变,但它本身是可以被销毁的
动态对象的生存期直到被释放时为止

delete之后重置指针值
当delete一个指针后,指针值就变成无效了,但指针仍然保存着动态内存(已经释放)的地址。在delete之后,指针就变成了空悬指针,指向一块曾经保存数据但现在已经无效的内存的指针。
避免空悬指针的方法:在指针即要离开其作用域之前释放掉它所关联的内存。这样,在指针关联的内存被释放掉之后,就没有机会继续使用指针了。如果我们需要保留指针,可以在delete之后将nullptr赋予指针。

这只是提供了有限的保护
动态内存的一个基本问题是可能有多个指针指向相同的内存。在delete内存之后重置指针的方法只对这个指针有效,对其他任何仍指向(已释放)内存的指针是没有用的

int *p = new int(42);
auto q = p;   //q和p指向相同的内存
delete p;     //q和p均变为无效
p = nullptr;  //指出P不再绑定到任何任何对象

但是重置p对于q没有任何作用,在我们释放p所指向的内存时,q也变为无效了。在实际系统中,查找相同内存的所有指针是异常困难的

编写函数,返回一个动态分配的int的vector,将此vector传递给另一个函数,这个函数读取标准输入,将读入的值保存在vector元素中。再将vector传递给另一个函数,打印读入的值,记得在恰当的时刻delete vector。

#include <iostream>
#include <vector>
using namespace std;
vector<int> *new_vector() {
	return new (nothrow) vector<int>;
}
void read_int(vector<int> *pv) {
	int v;
	while (cin >> v) {
		pv->push_back(v);
	}
	cin.clear();
}
void print_int(vector<int> *pv) {
	for (const auto& v : *pv) {
		cout << v << " ";
	}
	cout << endl;
}
int main() {
	vector<int>* pv = new_vector();
	if (!pv) {
		cout << "内存不足" << endl;
		return -1;
	}
	read_int(pv);
	print_int(pv);
	delete pv;
	pv = nullptr;
	return 0;
}

用shared_ptr而不是内置指针重写上面的函数

#include <iostream>
#include <vector>
#include <memory>
using namespace std;
shared_ptr<vector<int>> new_vector() {
	return make_shared<vector<int>>();
}
void read_int(shared_ptr<vector<int>> pv) {
	int v;
	while (cin >> v) {
		pv->push_back(v);
	}
	cin.clear();
}
void print_int(shared_ptr<vector<int>> pv) {
	for (const auto& v : *pv) {
		cout << v << " ";
	}
	cout << endl;
}
int main() {
	shared_ptr<vector<int>> pv = new_vector();
	if (!pv) {
		cout << "内存不足" << endl;
		return -1;
	}
	read_int(pv);
	print_int(pv);
	return 0;
}

解释下面代码执行的结果并说明存在的问题

int *q = new int(42), *r = new int(100);
r = q;

1、存在内存泄漏的问题,将q地址赋值给r,会导致q和r同时指向42的内存地址,而r中原来保存的地址将再无指针管理
2、容易造成指针空悬的问题。因为r和q指向同一个动态对象,如果程序编写不当,释放了其中一个指针,如果再使用另一个指针,就会造成无法预测的结果,因为该指针所指向的原内存已经被释放了

auto q2 = make_shared<int>(42), r2 = make_shared<int>(100);
r2 = q2;

用shared_ptr很好的解决了上面产生的两个问题。首先分配了两个共享的对象,分别由共享指针p2和q2指向,因此它们的引用计数均为1。接下来,将q2赋予r2,赋值操作会将q2指向对象的地址赋予r2,并将r2原来指向的对象的引用计数减1,将q2指向的对象的引用计数加1。这样,前者的引用计数为0,其占有的内存空间被释放,不会造成内存泄漏。而后者的引用计数变为2,也不会因为r2和q2之一的销毁而释放它的内存空间,因此也不会造成空悬指针的问题。

发布了72 篇原创文章 · 获赞 4 · 访问量 1661

猜你喜欢

转载自blog.csdn.net/CLZHIT/article/details/104045279