欢迎关注
0voice · GitHub
3
、构造函数析构函数可否抛出异常
C++
只会析构已经完成的对象,对象只有在其构造函数执⾏完毕才算是完全构造妥当。在构造函数中发⽣异常,控
制权转出构造函数之外。因此,在对象
b
的构造函数中发⽣异常,对象
b
的析构函数不会被调⽤。因此会造成内存
泄漏。
⽤
auto_ptr
对象来取代指针类成员,便对构造函数做了强化,免除了抛出异常时发⽣资源泄漏的危机,不再需要
在析构函数中⼿动释放资源;
如果控制权基于异常的因素离开析构函数,⽽此时正有另⼀个异常处于作⽤状态,
C++
会调⽤
terminate
函数让程
序结束;
如果异常从析构函数抛出,⽽且没有在当地进⾏捕捉,那个析构函数便是执⾏不全的。如果析构函数执⾏不全,就
是没有完成他应该执⾏的每⼀件事情。
4
、类如何实现只能静态分配和只能动态分配
前者是把
new
、
delete
运算符᯿载为
private
属性。
后者是把构造、析构函数设为
protected
属性,再⽤⼦类来动态创建
建⽴类的对象有两种⽅式:
静态建⽴,静态建⽴⼀个类对象,就是由编译器为对象在栈空间中分配内存;
动态建⽴,
A *p = new A();
动态建⽴⼀个类对象,就是使⽤
new
运算符为对象在堆空间中分配内存。这个过
程分为两步,第⼀步执⾏
operator new()
函数,在堆中搜索⼀块内存并进⾏分配;第⼆步调⽤类构造函数构
造对象;
只有使⽤
new
运算符,对象才会被建⽴在堆上,因此只要限制
new
运算符就可以实现类对象只能建⽴在栈
上。可以将
new
运算符设为私有。
1
、
C++
的
STL
介绍(内存管理,
allocator
,函数,实现机理,多线程实
现等)
STL
⼀共提供六⼤组件,包括容器,算法,迭代器,仿函数,配接器和配置器,彼此可以组合套⽤。容器通过配置
器取得数据存储空间,算法通过迭代器存取容器内容,仿函数可以协助算法完成不同的策略变化,配接器可以应⽤
于容器、 仿函数和迭代器。
容器:
各种数据结构,如
vector
,
list
,
deque
,
set
,
map
,⽤来存放数据, 从实现的⻆度来讲是⼀种类模板。
算法:
各种常⽤的算法,如
sort
(插⼊,快排,堆排序),
search
(⼆分查找), 从实现的⻆度来讲是⼀种⽅法
模板。
迭代器:
从实现的⻆度来看,迭代器是⼀种将
operator*,operator->,operator++, operator--
等指针相关操作赋予
᯿载的类模板,所有的
STL
容器都有⾃⼰的迭代器。
仿函数:
从实现的⻆度看,仿函数是⼀种᯿载了
operator()
的类或者类模板。 可以帮助算法实现不同的策略。
配接器:
⼀种⽤来修饰容器或者仿函数或迭代器接⼝的东⻄。
配置器:
负责空间配置与管理,从实现的⻆度讲,配置器是⼀个实现了动态空间配置、空间管理,空间释放的类模
板。
扩展:
内存管理
allocator
SGI
设计了
双层级配置器
,第⼀级配置器直接使⽤
malloc()
和
free()
完成内存的分配和回收。第⼆级配置器则根据
需求ᰁ的⼤⼩选择不同的策略执⾏。
对于
第⼆级配置器
,如果需求块⼤⼩⼤于
128bytes
,则直接转⽽调⽤第⼀级配置器,使⽤
malloc()
分配内存。如
果需求块⼤⼩⼩于
128bytes
,第⼆级配置器中维护了
16
个⾃由链表,负责
16
种⼩型区块的次配置能⼒。
即当有⼩于
128bytes
的需求块要求时,⾸先查看所需需求块⼤⼩所对应的链表中是否有空闲空间,如果有则直接
返回,如果没有,则向内存池中申请所需需求块⼤⼩的内存空间,如果申请成功,则将其加⼊到⾃由链表中。如果
内存池中没有空间,则使⽤
malloc()
从堆中进⾏申请,且申请到的⼤⼩是需求ᰁ的⼆倍(或⼆倍+
n
附加ᰁ),⼀
倍放在⾃由空间中,⼀倍(或⼀倍+
n
)放⼊内存池中。
如果
malloc()
也失败,则会遍历⾃由空间链表,四处寻找
“
尚有未⽤区块,且区块够⼤
”
的
freelist
,找到⼀块就挖出
⼀块交出。如果还是没有,仍交由
malloc()
处理,因为
malloc()
有
out-of-memory
处理机制或许有机会释放其他
的内存拿来⽤,如果可以就成功,如果不⾏就报
bad_alloc
异常
STL
中序列式容器的实现
:
vector
是动态空间,随着元素的加⼊,它的内部机制会⾃⾏扩充空间以容纳新元素。
vector
维护的是⼀个连续的线性空
间,⽽且普通指针就可以满⾜要求作为
vector
的迭代器(
RandomAccessIterator
)。
vector
的数据结构中其实就是三个迭 代器构成的,⼀个指向⽬前使⽤空间头的
iterator
,⼀个指向⽬前使⽤空间尾
的
iterator
,⼀个指向⽬前可⽤空间尾的
iterator
。当有新的元素插⼊时,如果⽬前容ᰁ够⽤则直接插⼊,如果容ᰁ
不够,则容ᰁ扩充⾄两倍,如果两倍容ᰁ不⾜, 就扩张⾄⾜够⼤的容ᰁ。
扩充的过程并不是直接在原有空间后⾯追加容ᰁ,⽽是᯿新申请⼀块连续空间,将原有的数据拷⻉到新空间中,再
释放原有空间,完成⼀次扩充。需要注意的是,每次扩充是᯿新开辟的空间,所以扩充后,原有的迭代器将会失
效。
list
与
vector
相⽐,
list
的好处就是每次插⼊或删除⼀个元素,就配置或释放⼀个空间,⽽且原有的迭代器也不会失
效。
STL list
是⼀个双向链表,普通指针已经不能满⾜
list
迭代器的需求,因为
list
的存储空间是不连续的。
list
的
迭代器必需具备前移和后退功能,所以
list
提供的是
BidirectionalIterator
。
list
的数据结构中只要⼀个指向
node
节点的指针就可以了。
deque
vector
是单向开⼝的连续线性空间,
deque
则是⼀种双向开⼝的连续线性空间。所谓双向开⼝,就是说
deque
⽀
持从头尾两端进⾏元素的插⼊和删除操作。相⽐于
vector
的扩充空间的⽅式,
deque
实际上更加贴切的实现了动
态空间的概念。
deque
没有容ᰁ的概念,因为它是动态地以分段连续空间组合⽽成,随时可以增加⼀段新的空间并
连接起来。
由于要维护这种整体连续的假象,并提供随机存取的接⼝(即也提供
RandomAccessIterator
),避开了
“
᯿新配
置,复制,释放
”
的轮回,代价是复杂的迭代器结构。也就是说除⾮必要,我们应该尽可能 的使⽤
vector
,⽽不是
deque
。
那么我们回过来具体说
deque
是如何做到维护整体连续的假象的,
deque
采⽤⼀块所谓的
map
作为主控,这⾥
的
map
实际上就是⼀块⼤⼩连续的空间,其中每⼀个元素,我们称之为节点
node
,都指向了另⼀段连续线性空间
称为缓冲区,缓冲区才是
deque
的真正存储空间主体。
SGI STL
是允许我们指定 缓冲区的⼤⼩的,默认
0
表示使⽤
512bytes
缓冲区。当
map
满载时,我们选⽤ ⼀块更
⼤的空间来作为
map
,᯿新调整配置。
deque
另外⼀个关键的就是它的
iterator
的设计,
deque
的
iterator
中有
四个部分,
cur
指向缓冲区现⾏元素,
first
指向缓冲区的头,
last
指向缓冲区的尾(有时会包含备⽤空间),
node
指向管控中⼼。所以总结来说,
deque
的数据结构中包含了,指向第⼀个节点的
iterator start
, 和指向最后⼀个节
点的
iterator finish
,⼀块连续空间作为主控
map
,也需要记住
map
的⼤⼩,以备判断何时配置更⼤的
map
。
stack
是⼀种先进后出的数据结构,只有⼀个出⼝,
stack
允许从最顶端新增元素,移除最顶端元素,取得最顶端元素。
deque
是双向开⼝的数据结构,所以使⽤
deque
作为底部结构并封闭其头端开⼝,就形成了⼀个
stack
。