【C++ Primer】查缺补漏(一)动态内存
文章目录
前言
本文是笔者回顾复习c++ primer时候对遗忘点的巩固笔记,非详细学习笔记
一、动态内存与智能指针
C++中的动态内存管理通过一对运算符完成:new
在动态内存中为对象分配空间并返回指向该对象的指针,可以选择对对象进行初始化;delete
接受一个动态对象的指针,销毁该对象并释放与之关联的内存。
新标准库提供了两种智能指针(smart pointer)类型来管理动态对象。智能指针的行为类似常规指针,但它自动释放所指向的对象。这两种智能指针的区别在于管理底层指针的方式:shared_ptr
允许多个指针指向同一个对象;unique_ptr
独占所指向的对象。标准库还定义了一个名为weak_ptr
的伴随类,它是一种弱引用,指向shared_ptr
所管理的对象。这三种类型都定义在头文件memory
中。
shared_ptr类
shared_ptr<string> p1; // shared_ptr that can point at a string
shared_ptr<list<int>> p2; // shared_ptr that can point at a list of ints
shared_ptr
独有的操作:
操作 | 含义 |
---|---|
p.use_count() | 返回p 的引用计数 |
p.unique() | 若p.use_count() == 1 ,则返回true ,一旦一个shared_ptr 的引用计数变为0,它就会自动释放其所管理的对象。 |
一些示例:
// shared_ptr that can point at a string
shared_ptr<string> p1;
// shared_ptr that can point at a list of
shared_ptr<list<int>> p2;
// shared_ptr that points to an int with value 42
shared_ptr<int> p3 = make_shared<int>(42);
// p4 points to a string with value 9999999999
shared_ptr<string> p4 = make_shared<string>(10, '9');
// p5 points to an int that is value initialized
shared_ptr<int> p5 = make_shared<int>();
// object to which p points has one user
auto p = make_shared<int>(42);
// p and q point to the same object
// object to which p and q point has two users
auto q(p);
程序使用动态内存通常出于以下三种原因:
-
不确定需要使用多少对象。
-
不确定所需对象的准确类型。
-
需要在多个对象间共享数据。
例子:定义StrBlob类
#include<vector>
#include<string>
#include<memory>
class StrBlob
{
public:
typedef std::vector<std::string>::size_type size_type;
StrBlob();
StrBlob(std::initializer_list<std::string> il);
size_type size() const { return data->size(); }
bool empty() const { return data->empty(); }
// 添加和修改元素
void push_back(const std::string& t) { data->push_back(t); }
void pop_back();
//元素访问
std::string& front();
std::string& back();
private:
std::shared_ptr<std::vector<std::string>> data;
void check(size_type i, const std::string& msg) const;
};
保证Blob的对象b1和b2中任意一个销毁时vector中的元素继续存在
构造函数
StrBlob::StrBlob(): data(make_shared<vector<string>>()){}
StrBlob::StrBlob()(initializer_list<std::string> il):
data(make_shared<vector<string>>()){}
shared_ptr
和new
结合使用
shared_ptr<int> p1 = new int(1024); // error: must use direct initialization
shared_ptr<int> p2(new int(1024)); // ok: uses direct initialization
不要混合使用内置指针和智能指针。当将shared_ptr
绑定到内置指针后,资源管理就应该交由shared_ptr
负责。不应该再使用内置指针访问shared_ptr
指向的内存。
// ptr is created and initialized when process is called
void process(shared_ptr<int> ptr)
{
// use ptr
} // ptr goes out of scope and is destroyed
int *x(new int(1024)); // dangerous: x is a plain pointer, not a smart pointer
process(x); // error: cannot convert int* to shared_ptr<int>
process(shared_ptr<int>(x)); // legal, but the memory will be deleted!
int j = *x; // undefined: x is a dangling pointer!
shared_ptr<int> p(new int(42)); // reference count is 1
process(p); // copying p increments its count; in process the reference count is 2
int i = *p; // ok: reference count is 1
也不要使用get
初始化另一个智能指针或为智能指针赋值。
shared_ptr<int> p(new int(42)); // reference count is 1
int *q = p.get(); // ok: but don't use q in any way that might delete its pointer
{ // new block
// undefined: two independent shared_ptrs point to the same memory
shared_ptr<int>(q);
} // block ends, q is destroyed, and the memory to which q points is freed
int foo = *p; // undefined; the memory to which p points was freed
unique_ptr
同一时刻只能有一个unique_ptr
指向给定的对象。当unique_ptr
被销毁时,它指向的对象也会被销毁。
unique_ptr<int> p1(new int(42));
// C++14
unique_ptr<int> p2 = make_unique<int>(42);
// transfers ownership from p1 (which points to the string Stegosaurus) to p2
unique_ptr<string> p2(p1.release()); // release makes p1 null
unique_ptr<string> p3(new string("Trex"));
// transfers ownership from p3 to p2
p2.reset(p3.release()); // reset deletes the memory to which p2 had pointed
p2.release(); // WRONG: p2 won't free the memory and we've lost the pointer
auto p = p2.release(); // ok, but we must remember to delete(p)
// p points to an object of type objT and uses an object of type delT to free that object
// it will call an object named fcn of type delT
unique_ptr<objT, delT> p (new objT, fcn);
void f(destination &d /* other needed parameters */)
{
connection c = connect(&d); // open the connection
// when p is destroyed, the connection will be closed
unique_ptr<connection, decltype(end_connection)*> p(&c, end_connection);
// use the connection
// when f exits, even if by an exception, the connection will be properly closed
}
二、动态数组
new
和数组
使用new
分配对象数组时需要在类型名之后跟一对方括号,在其中指明要分配的对象数量(必须是整型,但不必是常量)。new
返回指向第一个对象的指针(元素类型)。
// call get_size to determine how many ints to allocate
int *pia = new int[get_size()]; // pia points to the first of these ints
int *pia = new int[10]; // block of ten uninitialized ints
int *pia2 = new int[10](); // block of ten ints value initialized to 0
string *psa = new string[10]; // block of ten empty strings
string *psa2 = new string[10](); // block of ten empty strings
// block of ten ints each initialized from the corresponding initializer
int *pia3 = new int[10] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
// block of ten strings; the first four are initialized from the given initializers
// remaining elements are value initialized
string *psa3 = new string[10] { "a", "an", "the", string(3,'x') };
delete p; // p must point to a dynamically allocated object or be null
delete [] pa; // pa must point to a dynamically allocated array or be null
智能指针和动态数组
使用unique_ptr
可以直接管理动态数组,定义时需要在对象类型后添加一对空方括号[]
。
// up points to an array of ten uninitialized ints
unique_ptr<int[]> up(new int[10]);
up.release(); // automatically uses delete[] to destroy its pointer
与unique_ptr
不同,shared_ptr
不直接支持动态数组管理。如果想用shared_ptr
管理动态数组,必须提供自定义的删除器。
// to use a shared_ptr we must supply a deleter
shared_ptr<int> sp(new int[10], [](int *p) { delete[] p; });
sp.reset(); // uses the lambda we supplied that uses delete[] to free the array
shared_ptr
未定义下标运算符,智能指针类型也不支持指针算术运算。因此如果想访问shared_ptr
管理的数组元素,必须先用get
获取内置指针,再用内置指针进行访问。
// shared_ptrs don't have subscript operator and don't support pointer arithmetic
for (size_t i = 0; i != 10; ++i)
*(sp.get() + i) = i; // use get to get a built-in pointer
allocator
类
allocator
类是一个模板,定义时必须指定其可以分配的对象类型。
allocator<string> alloc; // object that can allocate strings
auto const p = alloc.allocate(n); // allocate n unconstructed strings
allocator
分配的内存是未构造的,程序需要在此内存中构造对象。新标准库的construct
函数接受一个指针和零或多个额外参数,在给定位置构造一个元素。额外参数用来初始化构造的对象,必须与对象类型相匹配。
auto q = p; // q will point to one past the last constructed element
alloc.construct(q++); // *q is the empty string
alloc.construct(q++, 10, 'c'); // *q is cccccccccc
alloc.construct(q++, "hi"); // *q is hi!
直接使用allocator
返回的未构造内存是错误行为,其结果是未定义的。
对象使用完后,必须对每个构造的元素调用destroy
进行销毁。destroy
函数接受一个指针,对指向的对象执行析构函数。
while (q != p)
alloc.destroy(--q); // free the strings we actually allocated
deallocate
函数用于释放allocator
分配的内存空间。传递给deallocate
的指针不能为空,它必须指向由allocator
分配的内存。而且传递给deallocate
的大小参数必须与调用allocator
分配内存时提供的大小参数相一致。
题外话
几个月后再回过头来复习这一章内容,代码基本上忘得差不多了,查缺补漏直接写成了学习笔记。。。真的是要不断巩固啊~
补充阅读
C/C++面试:什么是智能指针?智能指针有什么作用?分为哪几种?各自有什么样的特点?
C++——malloc/free和new/delete的区别