1、智能指针
1.1 两种智能指针shared_ptr
和 unique_ptr
共有操作:
- 注意:
get
函数返回的是内置类型的指针,不是智能指针
这样理解shared_ptr
:shared_ptr
类型的对象可以【共享所有权】,并且可以【分享该所有权】,一旦他们取得了一个指针的所有权,那么当该指针的这一组所有者中【最后一个释放所有权】的需要负责【释放该指针所指向的内存空间】。
shared_ptr
对象只能通过复制其值来共享所有权。
shared_ptr
独有的操作:
- 使用
make_shared
构造对象时,如果不传递任何参数,对象就会进行值初始化。 - 在刚刚初始化一个
shared_ptr
后,他的计数器是否为1或0,在于初始化的时候到底有没有给他一个实际分配了的堆内存空间。
2、直接管理内存(不用智能指针,而是用new
和 delete
)
2.1 使用new
动态分配和初始化对象
默认情况下(就是只写一个类型在那里),用new
动态分配的对象时默认初始化的。(注意不是值初始化)
但我们可以在初始化的时候使用 直接初始化(()
里面直接写上初值) 、构造函数初始化 、 列表初始化 、以及值初始化(只写一个()
)。
动态分配const
对象:
const int *pci = new const int(1024);
const string pcs = new const string;
内存耗尽:
int *p1 = new int; 如分配失败,new抛出异常std::bad_alloc
int *p2 = new (nothrow) int; 若分配失败,返回空指针,不抛出异常
2.2 delete
delete
用于释放内置指针指向的动态分配的对象所占用的动态内存空间。
delete
可以释放【任何】空指针,即使他不是指向动态内存delete
不能释放非new
定义的指针,准确来说是不能释放指向局部变量(栈空间)的指针delete
不能两次释放同一个指针- 不能释放非指针变量
const
对象也是可以被销毁的delete
一个指针后,指针值就无效了,虽说他还保存着(已经释放了的)动态内存的地址,但里面已经没有数据对象了。最好把它置为nullptr
2.3 练习题
答案和分析:
有错误。
上面的函数意图通过new
返回的指针来判断内存分配成功或失败,若成功,返回合法指针,指针转换成bool
类型的true
,若p
得到nullptr
则转换为false
。
但普通new
调用在【分配失败】时抛出一个【异常bad_alloc
】,而不是返回nullptr
,因此程序不能达到预期的目的。
进行如下修改阻止抛出异常:
int* p = new (nothrow) int;
不过最佳的方法是捕获异常,或判断返回的指针来返回true
或 false
,而不是依赖类型转换。
3、shared_ptr
和 new
的混合使用
可以用new
返回的指针初始化智能指针,如下所示:
shared_ptr<int> p(new int(42)); p2指向值为42的int
但需要注意:只能使用直接初始化方式。
因为接收参数的智能指针构造函数是explicit
的,所以不能将一个内置指针隐式转换为一个智能指针,必须直接初始化:
shared_ptr<int> p1 = new int(42); 错误,隐式地要求用int*来转换成shared_ptr
shared_ptr<int> p2(new int(42)); 正确
shared_ptr<int> clone(int p) 尤其在函数里要小心这种隐式转换
{
return new int(p); 隐式转换为shared_ptr
}
同理,一个shared_ptr
的指针,也不能隐式的转换为一个普通(内置)指针。
4、shared_ptr
的其他构造方法 以及 成员
最好不用内置指针访问之智能指针所指的对象,因为不知道对象和时会被销毁。内置指针很可能会变成悬空的。
5、使用智能指针的注意事项
6、unique_ptr
特有的操作
6.1 初始化方法
不可赋值初始化,或拷贝初始化。
unique_ptr<int> u1(new int(42)); 通过构造函数,直接初始化
unique_ptr<int> u2 = move(u1); 通过移动函数初始化,当然了,u1已经无了
6.2 unique_ptr
的操作
unique_ptr
和对象绑定在一起,所以没有拷贝 和 赋值操作- 但是,成员
release
和reset
共同使用可移交指针的所有权 release
返回的是【内置】指针类型。- 像
release
这种【不会释放对象】的操作,我们应该用一个指针或智能指针保存release
返回的指针,防止其丢失,并且要记得delete
掉这个指针。 unique_ptr
也有get
方法,但通常仅用于拿到指针,然后访问内存里的数据,一旦unique_ptr
被释放了,这个指针就不要用了。
6.3 unique_ptr
不能拷贝的例外
例1:
unique_ptr<int> aaa()
{
unique_ptr<int> u1(new int());
// 函数的代码
return u1; 返回值时发生了拷贝
}
例2:
unique_ptr<int> bbb()
{
// 函数的代码
return unique_ptr<int>(new int(42)); 返回值时发生了拷贝
}
6.4 指向数组的unique_ptr
(动态数组)(不做重点)
- 若
u
指向动态分配的数组,调用u.release()
,会自动用delete[]
销毁其内存空间和指针。 shared_ptr
不支持管理动态数组,如果希望使用shared_ptr
管理,需要提供自定义的删除器。而且,shared_ptr
也不支持下标访问元素。必须通过get
获取内置指针来访问。
7.1 weak_ptr
weak_ptr
不共享内存对象的所有权,也就是,他不会控制所指对象的生存期,即使它指向了shared_ptr
所管理的对象,也不会另相应shared_ptr
计数器增加。这就叫weak
- 注意
lock
成员返回的是一个shared_ptr
,所以会递增相应的计数器
8、allocator
分配器
8.1 标准库分配器及其成员
- 不要用
allocate
返回的指针去构造,而是应该用另一个指针接收它的值,用另一个指针构造,开始的指针最后用来释放内存。 construct
没有返回值,所以每构造完一个对象,你都得手动++
指针,只想下一片内存,才能继续构造。
allocator<int> alloc;
auto const p = alloc.allocate(10);
auto m = p;
alloc.construct(m++); 构造空 string
alloc.construct(m++, 10, 'c'); 10个'c'的string 'construct'的'args'参数可以是这些
alloc.construct(m++, "hi"); "hi"
while (m != p)
alloc.destroy(--q); 释放的时候,先 'destroy'
alloc.deallocate(p, 10); 最后一步
8.2 allocator
算法
- 经检验,虽说上述4个函数的参数
b
和e
是说传迭代器,但是传递一个指针也是没有问题的,指针的类型就是allocate
返回的指针类型。就是下面的这一长串:
class std::basic_string<char,struct std::char_traits<char>,class std::allocator<char> > *