动态分配
好有趣且卡哇伊的图
动态内存
程序用堆(heap)来存储动态分配(dynamically allocate)的对象。动态对象的生存期由程序控制。
动态内存与智能指针(Dynamic Memory and Smart Pointers)
C++中的动态内存管理通过一对运算符完成:new
在动态内存中为对象分配空间并返回指向该对象的指针,可以选择对对象进行初始化;delete
接受一个动态对象的指针,销毁该对象并释放与之关联的内存。
新标准库提供了两种智能指针(smart
pointer)类型来管理动态对象。智能指针的行为类似常规指针,但它自动释放所指向的对象。这两种智能指针的区别在于管理底层指针的方式:shared_ptr
允许多个指针指向同一个对象;unique_ptr
独占所指向的对象。标准库还定义了一个名为weak_ptr
的伴随类,它是一种弱引用,指向shared_ptr
所管理的对象。这三种类型都定义在头文件memory
中。
shared_ptr
类(The
shared_ptr
Class)
智能指针是模板,创建时需要指明指针可以指向的类型。默认初始化的智能指针中保存着一个空指针。
1 | shared_ptr<string> p1; // shared_ptr that can point at a string |
shared_ptr
和unique_ptr
都支持的操作:
操作 | 含义 |
---|---|
shared_ptr<T> sp
unique_ptr<T> up |
默认初始化为空指针 |
p |
若p 指向一个对象,则为true |
*p |
解引用p |
p->mem |
等价于(*p).mem |
p.get() |
返回p 中的指针 |
shared_ptr
独有的操作:
操作 | 含义 |
---|---|
p.use_count() |
返回p 的引用计数 |
p.unique() |
若p.use_count() == 1 ,则返回true |
1 | // shared_ptr that points to an int with value 42 |
这段代码使用了C++11中的智能指针
std::shared_ptr
和std::make_shared
函数来创建三个智能指针,并分别指向了不同类型的对象。让我逐步解释:
shared_ptr<int> p3 = make_shared<int>(42);
: 这行代码创建了一个shared_ptr
智能指针p3
,并使用make_shared
函数将其初始化为指向一个整型对象,值为42。make_shared<int>(42)
创建了一个动态分配的整型对象,并将其初始化为42,并且返回一个指向该对象的shared_ptr
。因此,p3
现在指向了这个整型对象。
shared_ptr<string> p4 = make_shared<string>(10, '9');
: 这行代码创建了一个shared_ptr
智能指针p4
,并使用make_shared
函数将其初始化为指向一个字符串对象,该字符串由10个字符'9'组成。make_shared<string>(10, '9')
创建了一个动态分配的字符串对象,并用字符'9'重复初始化10次,返回一个指向该字符串对象的shared_ptr
。因此,p4
现在指向了这个字符串对象。
shared_ptr<int> p5 = make_shared<int>();
: 这行代码创建了一个shared_ptr
智能指针p5
,并使用make_shared
函数将其初始化为指向一个整型对象,该整型对象是通过值初始化得到的。make_shared<int>()
创建了一个动态分配的整型对象,并执行值初始化操作,将其值设为0(整型的默认初始化值),返回一个指向该整型对象的shared_ptr
。因此,p5
现在指向了这个整型对象。综上所述,这段代码使用
std::make_shared
函数创建了三个shared_ptr
智能指针,分别指向一个整型对象、一个字符串对象和一个值初始化的整型对象。这样做可以保证动态分配的对象会被正确地管理,并且避免了手动管理内存的复杂性。
进行拷贝或赋值操作时,每个shared_ptr
会记录有多少个其他shared_ptr
与其指向相同的对象。
1 | auto p = make_shared<int>(42); // object to which p points has one user |
这段代码展示了如何使用
std::make_shared
创建shared_ptr
智能指针,并且演示了共享指针的引用计数机制。让我逐步解释:
auto p = make_shared<int>(42);
: 这行代码创建了一个shared_ptr
智能指针p
,并使用make_shared
函数将其初始化为指向一个整型对象,值为42。这个对象的引用计数为1,因为此时只有p
指向它。
auto q(p);
: 这行代码创建了另一个shared_ptr
智能指针q
,并将其初始化为指向p
所指向的对象。这时候,p
和q
都指向同一个对象,因此这个对象的引用计数增加到2。
每个shared_ptr
都有一个与之关联的计数器,通常称为引用计数(reference
count)。拷贝shared_ptr
时引用计数会递增。例如使用一个shared_ptr
初始化另一个shared_ptr
,或将它作为参数传递给函数以及作为函数的返回值返回。给shared_ptr
赋予新值或shared_ptr
被销毁时引用计数会递减。例如一个局部shared_ptr
离开其作用域。一旦一个shared_ptr
的引用计数变为0,它就会自动释放其所管理的对象。(感觉像py,也许是先学py的先入为主)
1 | auto r = make_shared<int>(42); // int to which r points has one user |
这段代码涉及了智能指针的赋值操作,它展示了当一个
shared_ptr
指向另一个对象时,引用计数的变化。让我逐步解释:
auto r = make_shared<int>(42);
: 这行代码创建了一个shared_ptr
智能指针r
,并使用make_shared
函数将其初始化为指向一个整型对象,值为42。这个对象的引用计数为1,因为此时只有r
指向它。
r = q;
: 这行代码将r
赋值为q
,即让r
指向q
所指向的对象。由于q
和r
都是shared_ptr
智能指针,赋值操作会使得两个智能指针共享同一个对象。在这里,r
原本指向的对象(值为42的整型对象)的引用计数减少到0,因为没有任何指针指向它,所以会被自动释放。而q
指向的对象的引用计数增加到3,因为此时有p
、q
和r
三个智能指针指向它。综上所述,这段代码展示了赋值操作对智能指针引用计数的影响:当一个智能指针指向另一个对象时,原对象的引用计数会减少,新对象的引用计数会增加,从而确保内存资源的正确管理。
shared_ptr
的析构函数会递减它所指向对象的引用计数。如果引用计数变为0,shared_ptr
的析构函数会销毁对象并释放空间。
如果将shared_ptr
存放于容器中,而后不再需要全部元素,而只使用其中一部分,应该用erase
删除不再需要的元素。
程序使用动态内存通常出于以下三种原因之一:
- 不确定需要使用多少对象。
- 不确定所需对象的准确类型。
- 需要在多个对象间共享数据。
直接管理内存(Managing Memory Directly)
相对于智能指针,使用new
和delete
管理内存很容易出错。
默认情况下,动态分配的对象是默认初始化的。所以内置类型或组合类型的对象的值将是未定义的,而类类型对象将用默认构造函数进行初始化。
1 | string *ps = new string; // initialized to empty string |
可以使用值初始化方式、直接初始化方式、传统构造方式(圆括号()
)或新标准下的列表初始化方式(花括号{}
)初始化动态分配的对象。
1 | int *pi = new int(1024); // object to which pi points has value 1024 |
只有当初始化的括号中仅有单一初始化器时才可以使用auto
。
1 | auto p1 = new auto(obj); // p points to an object of the type of obj |
这段代码展示了使用
auto
关键字与new
运算符结合动态创建对象的方法。让我逐步解释:
auto p1 = new auto(obj);
: 这行代码使用了auto
关键字来推断指针p1
所指向对象的类型,并且通过new
运算符动态地创建了这个对象。对象的类型与obj
相同,并且通过obj
进行初始化。换句话说,p1
指向了一个类型与obj
相同,并且由obj
初始化的对象。
auto p2 = new auto{a,b,c};
: 这行代码尝试使用auto
关键字来推断指针p2
所指向对象的类型,并且通过{a, b, c}
进行初始化。然而,这里使用了花括号{}
来初始化对象,但是这种初始化方式在使用auto
关键字时是不允许的,必须使用圆括号()
。因此,这行代码会导致编译错误。
可以用new
分配const
对象,返回指向const
类型的指针。动态分配的const
对象必须初始化。
默认情况下,如果new
不能分配所要求的内存空间,会抛出bad_alloc
异常。使用定位new
(placement
new)可以阻止其抛出异常。定位new
表达式允许程序向new
传递额外参数。如果将nothrow
传递给new
,则new
在分配失败后会返回空指针。bad_alloc
和nothrow
都定义在头文件new
中。
1 | // if allocation fails, new returns a null pointer |
使用delete
释放一块并非new
分配的内存,或者将相同的指针值释放多次的行为是未定义的。
由内置指针管理的动态对象在被显式释放前一直存在。
注意下面一点:
delete
一个指针后,指针值就无效了(空悬指针,dangling
pointer)。为了防止后续的错误访问,应该在delete
之后将指针值置空。
shared_ptr
和new
结合使用(Using
shared_ptr
s with new
)e
可以用new
返回的指针初始化智能指针。该构造函数是explicit
的,因此必须使用直接初始化形式。
1 | shared_ptr<int> p1 = new int(1024); // error: must use direct initialization |
这段代码展示了使用
std::shared_ptr
来管理动态分配的整型对象。让我逐步解释:
shared_ptr<int> p1 = new int(1024);
: 这行代码试图使用std::shared_ptr
来管理动态分配的整型对象,但采用了拷贝初始化的方式。然而,std::shared_ptr
的构造函数并没有接受一个裸指针作为参数的版本,因此这种方式是错误的。C++中的智能指针需要使用直接初始化的方式来构造,而不能使用拷贝初始化。
shared_ptr<int> p2(new int(1024));
: 这行代码使用了直接初始化的方式来创建一个std::shared_ptr
对象p2
,并让其指向一个动态分配的整型对象,值为1024。这种方式是正确的,因为std::shared_ptr
的构造函数具有接受裸指针作为参数的重载版本,用于直接初始化指针。这样,p2
就成功地管理了动态分配的整型对象,并在适当的时候自动释放内存,以避免内存泄漏。综上所述,正确的方式是使用直接初始化来创建
std::shared_ptr
对象,以确保正确地管理动态分配的内存。
默认情况下,用来初始化智能指针的内置指针必须指向动态内存,因为智能指针默认使用delete
释放它所管理的对象。如果要将智能指针绑定到一个指向其他类型资源的指针上,就必须提供自定义操作来代替delete
。
不要混合使用内置指针和智能指针。当将shared_ptr
绑定到内置指针后,资源管理就应该交由shared_ptr
负责。不应该再使用内置指针访问shared_ptr
指向的内存。
1 | // ptr is created and initialized when process is called |
这段代码展示了关于智能指针和裸指针之间的一些潜在问题。让我逐步解释:
int *x(new int(1024));
: 这行代码创建了一个动态分配的整型对象,并将其地址赋给了裸指针x
。裸指针不具备智能指针的内存管理功能,需要手动释放内存。
process(x);
: 这行代码试图将裸指针x
传递给接受shared_ptr<int>
类型参数的函数process()
。然而,由于process()
函数需要的是一个智能指针,因此无法将裸指针传递给它,会导致编译错误。
process(shared_ptr<int>(x));
: 这行代码通过使用shared_ptr<int>
的临时对象来传递裸指针x
给process()
函数。这样做是合法的,但是需要注意的是,shared_ptr
会假定它所管理的资源是通过new
创建的,因此会尝试释放x
所指向的内存,这样做会导致内存泄漏或者程序崩溃,因为x
不是通过new
创建的。
int j = *x;
: 这行代码试图使用裸指针x
来访问其所指向的内存中的值。然而,由于之前已经将x
所指向的内存释放了,因此x
成为了悬空指针,访问它的值会导致未定义行为。
shared_ptr<int> p(new int(42));
: 这行代码创建了一个动态分配的整型对象,并用shared_ptr<int>
对象p
来管理它。智能指针p
具有内存管理功能,可以确保在适当的时候自动释放内存。
process(p);
: 这行代码将shared_ptr<int>
对象p
传递给了process()
函数。智能指针p
的引用计数会增加,因为process()
函数会对其进行拷贝,这样在函数内部也会有一个指向相同内存的智能指针。
int i = *p;
: 这行代码试图使用智能指针p
来访问其所管理的内存中的值。由于智能指针p
是有效的,它所管理的内存也是有效的,因此可以正常地使用p
来访问其所指向的值。
智能指针的get
函数返回一个内置指针,指向智能指针管理的对象。主要用于向不能使用智能指针的代码传递内置指针。使用get
返回指针的代码不能delete
此指针。
不要使用get
初始化另一个智能指针或为智能指针赋值。
1 | shared_ptr<int> p(new int(42)); // reference count is 1 |
这段代码涉及了在使用
shared_ptr
时通过get()
函数获取其内部裸指针的情况。让我逐步解释:
shared_ptr<int> p(new int(42));
: 这行代码创建了一个动态分配的整型对象,并用shared_ptr<int>
对象p
来管理它。智能指针p
的引用计数为1。
int *q = p.get();
: 这行代码通过get()
函数获取了shared_ptr
对象p
内部所指向的裸指针,并将其赋值给了裸指针q
。但是需要注意的是,尽管q
指向了与p
相同的内存地址,但它不会增加引用计数。get()
函数的目的是为了兼容传统接口,但是使用裸指针q
的任何地方都需要格外小心,以避免在p
的生命周期内使用了已经释放的内存。
{}
内的新块: 在这个新块中,创建了一个匿名的shared_ptr<int>
对象,其构造函数接受了裸指针q
。由于这个shared_ptr
对象是在一个新的作用域中创建的,所以它会在块结束时被销毁。因此,当块结束时,引用计数会减少,并且由q
所指向的内存也会被释放。
int foo = *p;
: 这行代码试图使用智能指针p
来访问其所管理的内存中的值。然而,在上一步中,由于块结束时释放了q
所指向的内存,因此p
现在成为了悬空指针,访问它的值将导致未定义行为。综上所述,尽管使用
get()
函数获取了智能指针的裸指针,但需要特别小心地使用裸指针,以确保不会在智能指针的生命周期内使用已释放的内存,避免出现未定义行为。
可以用reset
函数将新的指针赋予shared_ptr
。与赋值类似,reset
会更新引用计数,如果需要的话,还会释放内存空间。reset
经常与unique
一起使用,来控制多个shared_ptr
共享的对象。
1 | if (!p.unique()) |
智能指针和异常(Smart Pointers and Exceptions)
如果使用智能指针,即使程序块过早结束,智能指针类也能确保在内存不再需要时将其释放。
1 | void f() |
默认情况下shared_ptr
假定其指向动态内存,使用delete
释放对象。创建shared_ptr
时可以传递一个(可选)指向删除函数的指针参数,用来代替delete
。
1 | struct destination; // represents what we are connecting to |
智能指针规范:
- 不使用相同的内置指针值初始化或
reset
多个智能指针。 - 不释放
get
返回的指针。 - 不使用
get
初始化或reset
另一个智能指针。 - 使用
get
返回的指针时,如果最后一个对应的智能指针被销毁,指针就无效了。 - 使用
shared_ptr
管理并非new
分配的资源时,应该传递删除函数。
unique_ptr
(unique_ptr
)
与shared_ptr
不同,同一时刻只能有一个unique_ptr
指向给定的对象。当unique_ptr
被销毁时,它指向的对象也会被销毁。
make_unique
函数(C++14新增,定义在头文件memory
中)在动态内存中分配一个对象并初始化它,返回指向此对象的unique_ptr
。
1 | unique_ptr<int> p1(new int(42)); |
由于unique_ptr
独占其指向的对象,因此unique_ptr
不支持普通的拷贝或赋值操作。
release
函数返回unique_ptr
当前保存的指针并将其置为空。
reset
函数成员接受一个可选的指针参数,重新设置unique_ptr
保存的指针。如果unique_ptr
不为空,则它原来指向的对象会被释放。
1 | // transfers ownership from p1 (which points to the string Stegosaurus) to p2 |
这段代码展示了如何使用
release()
和reset()
函数在unique_ptr
之间转移所有权。让我逐步解释:
unique_ptr<string> p2(p1.release());
:
p1.release()
函数释放了p1
所管理的内存,同时返回了指向该内存的指针,并且将p1
置空(指向nullptr
)。- 然后,将这个指针传递给了
unique_ptr
构造函数,用于构造一个新的unique_ptr
对象p2
,从而完成了所有权的转移。现在,p2
拥有了原先由p1
管理的内存,而p1
不再拥有该内存。unique_ptr<string> p3(new string("Trex"));
:
- 创建了一个动态分配的字符串对象,并用 "Trex" 来初始化它,并将其管理权交给
unique_ptr
对象p3
。p2.reset(p3.release());
:
p3.release()
函数释放了p3
所管理的内存,并返回了指向该内存的指针,同时将p3
置空。- 然后,
reset()
函数接受这个指针作为参数,并释放了p2
所管理的内存,然后将p2
重新指向了这块新的内存。这样,所有权从p3
转移到了p2
。综上所述,
release()
函数将unique_ptr
对象的所有权释放,并返回指向管理的内存的指针,同时将unique_ptr
置空。而reset()
函数允许将unique_ptr
重新指向新的内存,并释放原先管理的内存。通过这两个函数的组合使用,可以实现unique_ptr
之间的所有权转移。
调用release
会切断unique_ptr
和它原来管理的对象之间的联系。release
返回的指针通常被用来初始化另一个智能指针或给智能指针赋值。如果没有用另一个智能指针保存release
返回的指针,程序就要负责资源的释放。
1 | p2.release(); // WRONG: p2 won't free the memory and we've lost the pointer |
这段代码使用了
release()
函数来释放unique_ptr
对象p2
所管理的内存,但是存在一些问题。让我详细解释一下:
p2.release();
:
- 这行代码调用了
release()
函数,该函数释放了p2
所管理的内存,并返回了指向该内存的指针。但是需要注意的是,release()
函数只是释放了内存,而并没有将unique_ptr
置空。- 这是一个错误的用法,因为
release()
函数只是释放了内存,而并没有将unique_ptr
置空,所以p2
仍然持有对原始内存的所有权,但是丢失了对原始指针的控制,这样会导致内存泄漏。auto p = p2.release();
:
- 这行代码调用了
release()
函数,将p2
所管理的内存释放,并返回了指向该内存的指针。然后,使用auto
关键字声明一个指针p
,将返回的指针赋给了p
。- 这种用法也是正确的,但是需要注意的是,现在需要手动管理内存的释放。在使用
auto
声明的指针p
后,需要在适当的时候使用delete p
手动释放内存,否则会导致内存泄漏。总的来说,
release()
函数用于释放unique_ptr
对象所管理的内存,并返回指向该内存的裸指针,但是需要注意确保在适当的时候手动管理内存的释放,以避免内存泄漏。
不能拷贝unique_ptr
的规则有一个例外:可以拷贝或赋值一个即将被销毁的unique_ptr
(移动构造、移动赋值)。
1 | unique_ptr<int> clone(int p) |
weak_ptr
(weak_ptr
)
weak_ptr
是一种不控制所指向对象生存期的智能指针,它指向一个由shared_ptr
管理的对象。将weak_ptr
绑定到shared_ptr
不会改变shared_ptr
的引用计数。如果shared_ptr
被销毁,即使有weak_ptr
指向对象,对象仍然有可能被释放。
操作 | 含义 |
---|---|
weak_ptr<T> w(sp) |
w 与shared_ptr sp 指向相同对象 |
w.use_count() |
返回与w 共享对象的shared_ptr 的数量· |
w.expired() |
若w.use_count() == 0 ,则返回true |
w.lock() |
若w.expired() == true ,则返回空shared_ptr ;否则返回一个指向w 的对象的shared_ptr |
创建一个weak_ptr
时,需要使用shared_ptr
来初始化它。
1 | auto p = make_shared<int>(42); |
使用weak_ptr
访问对象时,必须先调用lock
函数。该函数检查weak_ptr
指向的对象是否仍然存在。如果存在,则返回指向共享对象的shared_ptr
,否则返回空指针。
1 | if (shared_ptr<int> np = wp.lock()) |
动态数组(Dynamic Arrays)
使用allocator
类可以将内存分配和初始化过程分离,这通常会提供更好的性能和更灵活的内存管理能力。
new
和数组(new
and Arrays)
使用new
分配对象数组时需要在类型名之后跟一对方括号,在其中指明要分配的对象数量(必须是整型,但不必是常量)。new
返回指向第一个对象的指针(元素类型)。
1 | // call get_size to determine how many ints to allocate |
由于new
分配的内存并不是数组类型,因此不能对动态数组调用begin
和end
,也不能用范围for
语句处理其中的元素。
默认情况下,new
分配的对象是默认初始化的。可以对数组中的元素进行值初始化,方法是在大小后面跟一对空括号()
。在新标准中,还可以提供一个元素初始化器的花括号列表。如果初始化器数量大于元素数量,则new
表达式失败,不会分配任何内存,并抛出bad_array_new_length
异常。
1 | int *pia = new int[10]; // block of ten uninitialized ints |
虽然可以使用空括号对new
分配的数组元素进行值初始化,但不能在括号中指定初始化器。这意味着不能用auto
分配数组。
动态分配一个空数组是合法的,此时new
会返回一个合法的非空指针。对于零长度的数组来说,该指针类似尾后指针,不能解引用。
使用delete[]
释放动态数组。
1 | delete p; // p must point to a dynamically allocated object or be null |
如果在delete
数组指针时忘记添加方括号,或者在delete
单一对象时使用了方括号,编译器很可能不会给出任何警告,程序可能会在执行过程中行为异常。
unique_ptr
可以直接管理动态数组,定义时需要在对象类型后添加一对空方括号[]
。
1 | // up points to an array of ten uninitialized ints |
- 自动使用
delete[]
:
- 当
release()
函数被调用时,unique_ptr
对象会自动根据其类型来决定使用delete
还是delete[]
操作符来销毁其所管理的内存。- 对于
unique_ptr<int[]>
类型,unique_ptr
知道它所管理的是一个动态分配的数组,因此在调用release()
函数后,它会使用delete[]
操作符来销毁该数组。
与unique_ptr
不同,shared_ptr
不直接支持动态数组管理。如果想用shared_ptr
管理动态数组,必须提供自定义的删除器。
1 | // to use a shared_ptr we must supply a deleter |
shared_ptr<int> sp(new int[10], [](int *p) { delete[] p; });
:
- 这行代码创建了一个名为
sp
的shared_ptr
对象,用于管理一个动态分配的整型数组。- 构造函数的第一个参数是指向动态分配的数组的指针,第二个参数是一个 lambda 表达式,用于自定义删除器。
- lambda 表达式
[](int *p) { delete[] p; }
接受一个int*
类型的指针参数,并使用delete[]
操作符来释放指向数组的内存。这样就确保了当shared_ptr
对象销毁时,使用自定义删除器来正确释放动态分配的数组内存。sp.reset();
:
- 这行代码调用了
reset()
函数,该函数将shared_ptr
对象sp
置空,释放其所管理的内存。- 在调用
reset()
函数时,shared_ptr
对象将会调用之前提供的自定义删除器 lambda 表达式,使用delete[]
操作符来释放动态分配的数组内存。- 这样可以确保在使用
shared_ptr
管理动态分配数组的情况下,内存能够正确释放,避免内存泄漏问题。
shared_ptr
未定义下标运算符,智能指针类型也不支持指针算术运算。因此如果想访问shared_ptr
管理的数组元素,必须先用get
获取内置指针,再用内置指针进行访问。
1 | // shared_ptrs don't have subscript operator and don't support pointer arithmetic |
allocator
类(The
allocator
Class)
allocator
类是一个模板,定义时必须指定其可以分配的对象类型。
1 | allocator<string> alloc; // object that can allocate strings |
这段代码创建了一个名为
alloc
的allocator<string>
对象,该对象可以用来分配字符串类型的内存空间。然后,使用allocate()
函数来分配了n
个未构造的字符串对象,并将分配的内存空间的起始地址赋值给了p
。
allocator
分配的内存是未构造的,程序需要在此内存中构造对象。新标准库的construct
函数接受一个指针和零或多个额外参数,在给定位置构造一个元素。额外参数用来初始化构造的对象,必须与对象类型相匹配。
1 | auto q = p; // q will point to one past the last constructed element |
auto q = p;
:
- 这行代码将
p
的值赋给了q
,使得q
指向分配的内存空间的起始位置。alloc.construct(q++);
:
- 这行代码调用了
construct()
函数,用于在q
指向的位置构造一个未初始化的字符串对象。q++
表示先使用q
的值进行构造,然后递增q
指向下一个位置。因此,此时q
指向下一个未构造的字符串对象的位置。- 构造后的字符串对象是空字符串,因为未提供任何参数来初始化它。
alloc.construct(q++, 10, 'c');
:
- 这行代码调用了
construct()
函数,用于在q
指向的位置构造一个未初始化的字符串对象。10, 'c'
是传递给construct()
函数的参数,用于初始化构造的字符串对象。这表示该字符串对象由10个字符'c'
组成。q++
递增了q
,使得q
指向下一个未构造的字符串对象的位置。alloc.construct(q++, "hi");
:
- 这行代码调用了
construct()
函数,用于在q
指向的位置构造一个未初始化的字符串对象。"hi"
是传递给construct()
函数的参数,用于初始化构造的字符串对象。这表示该字符串对象的内容为"hi"
。q++
递增了q
,使得q
指向下一个未构造的字符串对象的位置。
直接使用allocator
返回的未构造内存是错误行为,其结果是未定义的。
对象使用完后,必须对每个构造的元素调用destroy
进行销毁。destroy
函数接受一个指针,对指向的对象执行析构函数。
1
2 while (q != p)
alloc.destroy(--q); // free the strings we actually allocated
deallocate
函数用于释放allocator
分配的内存空间。传递给deallocate
的指针不能为空,它必须指向由allocator
分配的内存。而且传递给deallocate
的大小参数必须与调用allocator
分配内存时提供的大小参数相一致。
1 | alloc.deallocate(p, n); |
czs108/Cpp-Primer-5th-Notes-CN: 📚 《C++ Primer中文版(第5版)》笔记 (github.com)