可编程内存基本上可分为以后三个大部分:
动态内存与智能指针:
c++中动态内存时通过一对运算符来完成的:new,在动态内存中为对象分配内存空间并返回一个指向该对象的指针,我们可以选择对象进行初始化;delete,接受一个动态对象的指针,销毁该对象并释放与之关联的内存。
c++11标准库中提供了两种智能指针类型来管理动态内存对象。智能指针的行为类似常规指针,重要的区别是它负责自动释放所指向的对象。标准库提供的这两种智能指针的区别在于管理底层指针的方式:shared_ptr 允许多个指针指向同一个对象;unique_ptr 则 “独占” 所指向的对象。标准库还定义了一个名为 weak_ptr 的伴随类,它是一个弱引用,指向 shared_ptr 所管理的对象。
默认情况下,动态内存new分配的对象是默认初始化的,即,内置类型或组合类型的对象的值将是未定义的(随机值),而类类型对象将用默认构造函数进行初始化。
释放内存时,传递给 delete 的指针必须指向动态分配的内存,或者是一个空指针。
{
int* pi = new int;
delete pi;
}
{
int* pi = new int[3];
//delete pi; //对于基本数据结构,也能正确。
delete[] pi; // 应该正确使用数组的释放方法 delete[] obj
}
{
int* pi = new int[3]{ 0,1,2 }; // 列表初始化
delete[] pi;
std::string* ps = new string[2]{ "abc","123" };
//delete ps; // 报错
delete[] ps;
}
{
int obj = 1024;
int obj1 = 1, obj2 = 2, obj3 = 3;
auto p1 = new auto(obj);
//auto p2 = new auto{obj1,obj2,obj3}; //vs2015不能编译成功
}
内存耗尽的处理
try {
// 内存不够,默认情况选会抛出一个类型为 bad_alloc 的异常。
// 需要手动捕获错误,使程序正常执行
int* pi = new int[std::numeric_limits<long long>::max()];
std::cout << pi << std::endl;
}
catch (std::exception& e) {
std::cout << e.what() << endl;
}
{
// 使用定位new,在内存分配失败时不抛出异常,返回空指针
int* pi = new(nothrow) int[std::numeric_limits<long long>::max()];
std::cout << pi << std::endl; //0x0
}
delete 之后重置指针:
delete之后指针就变成了空悬指针(不为空),若还有其他对象在使用(包括二次释放)会造成未定义的错误。若无法保证是否还有其他对象使用,在 delete 之后将 nullptr 赋予指针,这样就清楚地指出指针不指向任何对象。
另外,重复delete一个nullptr指针是安全的。
int *p = new int();
*p = 1;
delete p; // delete后p为野指针
{
p = nullptr;
delete p; // delete一个空指针是没有错误的
}
智能指针也是模板,使用方式与普通指针类似。解引用一个智能指针返回它指向的对象。如果在一个判断条件中使用智能指针,效果就是检查它是否为空。
最安全的分配和使用动态内存的方法是调用一个名为 make_shared 的标准库函数。
{
std::shared_ptr<std::string> p1;//shared_ptr,可以指向string
std::shared_ptr<std::list<int>> p2;//shared_ptr,可以指向int的list
if(p1 && p1->empty())
*p1 = "hi"; //如果p1指向一个空string,解引用p1,将一个新值赋予string
}
{
std::shared_ptr<char*> pChar; // 空指针
pChar = std::make_shared<char*>(new char[10]);
auto xxx = *pChar; //解引用,指向管理对象,也就是char*内存
auto yyy = pChar.get(); // 同xxx, 返回对象指针
}
当进行拷贝和赋值操作时,每个 shared_ptr 都会记录有多少个其它 shared_ptr 指向相同的对象。每个 shared_ptr 都有一个关联的计数器,通常称其为引用计数。无论何时我们拷贝一个 shared_ptr,计数器都会递增。如,当用一个 shared_ptr 初始化另一个 shared_ptr,或将它作为参数传递给另一个函数以及作为函数的返回值时,它所关联的计数器都会递增。当我们给 shared_ptr 赋予一个新值或是 shared_ptr 被销毁(如,一个局部的 shared_ptr 离开其作用域)时,计数器都会递减。一旦一个 shared_ptr 的计数器变为 0,它就会自动释放自己所管理的对象(基本数据直接释放内存,类对象通常会自动调用其析构函数)。
{
std::shared_ptr<int> ptr1(new int(1)); // 引用计数为1
std::shared_ptr<int> ptr2 = ptr1; // ptr2共享ptr1的指向对象, 引用计数为2
ptr2.reset(); // ptr2置为空 //引用计数减为1
int *pi = new int(2);
ptr2.reset(pi); // ptr2指向pi内存
}
使用了动态生存,处于以下三种原因之一:
1.程序不知道自己需要使用多少对象
2.程序不知道所需对象的准确类型
3.程序需要在多个对象间共享数据
分析第 3 种情况:之前用过的类中,分配的资源都与对应对象生存期一致。如:每个 vector “拥有” 其自己的元素。当我们拷贝一个 vector 时,原 vector 和 副本 vector 中的元素是相互分离的:
vector<string> v1;//空vector
{//新作用域
vector<string> v2 = {"f", "jf"};
v1 = v2;//从v2拷贝元素到v1中
}//离开作用域,v2被销毁,其中元素也被销毁
//v1中有2个元素,是原来v2中元素的拷贝
但某些类分配的资源具有与原对象相独立的生存期。如,假定我们希望定义一个名为 Blob 的类,保存一组元素。与容器不同,我们希望 Blob 对象的不同拷贝之间共享相同的元素。即,当我们拷贝一个 Blob 时,原 Blob对象及其拷贝应该引用相同的底层元素。
通常,如果两个对象共享底层数据,当某个对象被销毁时,我们不能单方面销毁底层数据:
Blob<string> b1;//空Blod
{//新作用域
Blob<string> b2 = {"f", "jf"};
v1 = v2;//从b2拷贝元素到b1中
}//离开作用域,b2被销毁,但其中元素不能销毁
//b1指向最初由b2创建的元素
注意:b1 和 b2 共享相同的元素,当 b2 离开作用域时,这些元素必须保留,因为 b1 仍任在使用它们
定义一个管理 string 的 Blob类,命名为 strBlob,当我们拷贝、赋值或销毁一个 strBlob 对象时,它的 shared_ptr 成员会被拷贝、赋值或销毁。因此,对于由 strBlod 构造函数分配的 vector,当最后一个指向它的 strBlob 对象被销毁时,它也会随之被自动销毁。
使用了动态生存期的资源的类StrBlob定义如下
class StrBlob {
public:
using size_type = std::vector<std::string>::size_type;
//StrBlob();
StrBlob(std::initializer_list<std::string> il) :
data(std::make_shared<std::vector<std::string>>(il))
{
} // 注意:使用了initializer_list初始化列表构造函数
size_type size() const { return data->size(); }
bool empty() const { return data->empty(); }
void push_back(const std::string& str) { data->push_back(str); }
void pop_back() { check(0, "pop_back on empty StrBlob"); data->pop_back(); }
// 这里没有修改成员对象的值,是调用成员的成员函数修改内部值
void pop_back()const { check(0, "pop_back on empty StrBlob"); data->pop_back(); }
//std::string& front(){ check(0, "front on empty StrBlob"); return data->front();}
//std::string& back() {check(0, "back on empty StrBlob"); return data->back();}
// const函数, 建议返回const引用
std::string& front() const { check(0, "front on empty StrBlob");return data->front(); }
std::string& back() const { check(0, "back on empty StrBlob"); return data->back(); }
private:
std::shared_ptr<std::vector<std::string>> data;
void check(size_type i, const std::string& msg) const
{
if (i >= data->size()) throw std::out_of_range(msg);
}
};
测试代码如下
{
StrBlob strBlob = { "123","abc" }; //初始化列表构造, ref = 1
strBlob.push_back(",.?");
while (!strBlob.empty()) {
auto& str = strBlob.back(); // 非const对象,先找非const函数实现,若无再找const函数
std::cout << str << std::endl;
strBlob.pop_back();
}
{
StrBlob b = strBlob; //ref = 2 data数据共用
b.push_back("a");
} // b释放后,由于底层数据共享,所有a中保留b操作添加的元素
strBlob.push_back("a"); // 两个元素 ["a","a"]
}
{
const StrBlob strBlob = { "123","abc" };
// const 对象只能使用const函数
while (!strBlob.empty()) {
auto& str = strBlob.back();
str = "aaa";
std::cout << str << std::endl;
strBlob.pop_back();
}
}
接受指针参数的智能指针构造函数是 explicit 的,我们不能将一个内置指针隐式转化一个智能指针,必须使用直接初始化形式来初始化一个智能指针。
{
std::shared_ptr<int> p1; // 空指针
//不能显示地转换
p1 = std::make_shared<int>(22);
//std::shared_ptr p2 = new int(21); //必须使用直接初始化
std::shared_ptr<int> p2 = std::make_shared<int>(21);
std::shared_ptr<int> p3(new int(20));
}
另外,可将shared_ptr绑定到一个想要返回的指针上
std::shared_ptr clone(int p)
{
return std::shared_ptr<int>(new int(p));
}
不要混合使用普通指针和智能指针:
当将一个 shared_ptr 绑定到一个普通指针时,我们就将内存的管理交给了这个 shared_ptr。一旦这样做了,我们就不应该再使用内置指针来访问 shared_ptr 所指向的内存了。
auto process = [](std::shared_ptr<int> ptr) {//值传参,拷贝后引用计数+1
*ptr += 1;
};//离开ptr作用域,ptr被销毁,其所指对象引用计数-1
{
std::shared_ptr<int> p(new int(1)); // ref 1
process(p); // 调用时ref 2, 返回时 ref 1
int res = *p;
}
{ // 不能传递一个内置指针给process函数,但是可以传递一个临时的shared_ptr
// 这个shared_ptr是内置指针显示构造,很可能导致错误
int *x(new int(1));
// process(x); // 不能从int* 转换为 shared_ptr
process(std::shared_ptr<int>(x)); // 用内置指针显示构造一个临时shared_ptr接管, 函数返回时会释放x的内存
int res = *x; // 未定义,x目前是一个空悬指针
}
不要使用 get 初始化另一个智能指针或为智能指针赋值:
get 通常用于将 shared_ptr 中保存的值赋给普通指针,只有在确定代码不会 delete 指针的情况下才能用 get。
两个相互独立但指向相同的内存的 shared_ptr 各自的引用计数都是 1,着意味着当其中一个自动销毁但另一个没被销毁时,没被销毁的 shared_ptr 会成为空悬指针。
{
// get()返回内置指针,用于不接受智能指针的代码,
std::shared_ptr<int> p(new int(1));
int *x = p.get(); //正确,但使用时要注意,不要让它管理的指针被释放
{//新作用域
std::shared_ptr<int>(x);//未定义,两个独立的shared_ptr指向相同的内存
}//程序块结束,p被销毁,它指向的内存被释放
int foo = *p; //未定义,p指向的内存已经被释放了
}
其它 shared_ptr 操作:
与赋值类似,reset 会更新引用计数,如果需要的话,会释放 p 指向的对象。reset 经常与 unique 其使用,来控制多个 shared_ptr 共享的对象。
{ // 修改智能指针的底层数据,先要判断是否有其他用户使用
std::shared_ptr<int> p(new int(1));
std::shared_ptr<int> p2(p);
if (!p.unique()) {
// 不是唯一的用户,分配新的拷贝
// p.reset(new int(2));
p = std::make_shared<int>(2); // 效果同上
}
*p += 1; //唯一用户,可以改变,不影响其他对象的值
std::cout << *p2 << " " << *p << std::endl; // 1 3
}
删除器:
当一个 shared_ptr 管理的对象被释放时,默认是进行 delete 操作,而 delete 操作是调用对应对象的析构函数来完成的。
但是,对于分配了资源而又没有定义析构函数的类,我们就需要定义一个函数来代替 delete 完成对 shared_ptr 中保存的指针进行释放操作,即删除器。
例如,定义一个具有分配资源的类但无自定义析构的类DataMem
class DataMem {
public:
//DataMem();
DataMem(size_t sz) : p(new int[10]) {}
// ~DataMem() { release(); }
int* getPtr() { return p; }
void release() { delete[] p; p = nullptr; }
private:
int* p;
};
使用内置指针管理内存,且在 new 之后再对应的 delete 之前发生了异常,则内存不会被释放,如下。
void f(){
int ip = new int(42);
//...//此处发生一个异常且未在f中被捕获
delete ip; // 退出前释放内存
}
在这里,当函数退出(正常结束或发生异常)时,无析构函数时的类DataMem对象都会被销毁,但是已经分配的资源不会释放。
int *ptr = nullptr;
try {
{
DataMem dm = DataMem(10); // 局部对象, 无析构函数
ptr = dm.getPtr();
//使用代码
// ....
throw 1; // 假装有异常 , 局部对象无析构函数,不会执行异常抛出后面的代码
// ....
// 不要忘记释放 (异常发生后不会执行,导致内存泄漏)
dm.release();
}
// {
// DataMem dm = DataMem(10); // 局部对象,有析构函数
// ptr = dm.getPtr();
// //使用代码
// // ....
// throw 1; // 假装有异常 , 局部对象有析构函数,会执行析构函数(用户确保对象动态内存释放)
// // ....
// }
{
// 创建一个删除器作为智能指针的自定义操作参数,类型为对象指针的函数
// 删除器原型 void funcName(DataMem* dm) { ...; }
auto ReleaseMem = [](DataMem* dm)-> void {
dm->release();
};
DataMem dm = DataMem(10); // 局部对象,无析构函数
//std::shared_ptr pdm(&dm, ReleaseMem); // 局部对象,无析构函数; 删除器;
std::shared_ptr<DataMem> pdm(&dm, [](DataMem* dm) {dm->release(); });
ptr = dm.getPtr();
//使用代码
// ....
throw 1; // 假装有异常 , 局部对象无析构,shared_ptr接管并负责用删除器回收内存
// ....
}
}
catch (int e) {
std::cout << "catch throw " << e << std::endl;
}
catch (std::exception& e) {
std::cout << e.what() << std::endl;
}
Tip:只要自己定义类时保证析构函数能正确释放类中分配资源,就不再需要使用删除器了。
智能指针的安全性是建立在正确使用的前提下的,我们必须坚持一些基本规范:
与 shared_ptr 不同,某个时刻只能有一个 unique_ptr 指向给定的对象。当 unique 被销毁时,它所指向的对象也被销毁。
基本操作
unique_ptr
unique_ptr
除了需要一个能转化成 T* 类型的对象外还需要一个 D 类型的对象来代替 delete
unique_ptr
u = nullptr 释放 u 指向的对象,将 u 置为空
u.release() u 放弃对指针的控制权,返回指针,并将 u 置为空
u.reset() 释放 u 指向的对象
u.reset(q) 如果提供了内置指针 q,令 u 指向这个对象,否则将 u 置为空
u.reset(nullptr)
由于一个 unique_ptr 拥有它指向的对象,因此 unique_ptr 不支持普通的拷贝或赋值操作。初始化 unique_ptr 采用直接初始化形式、或者类似make_shared的形式(c++14)。
基本使用方式
std::unique_ptr<int> p1;
p1 = std::unique_ptr<int>(new int(1));
//p1 = std::make_unique(2); //c++14
std::unique_ptr<int> p2(new int(3));
//p1 = p2; 不允许
//p1 = nullptr; // 释放p1指向对象
p1.reset(); // 释放p1指向对象,效果同上
int *ptr = p2.release(); // 仅放弃控制权,p2为空指针,并返回指向对象的内置指针
delete ptr; //若使用了release, 需要自己管理内存数据
如果 unique_ptr 不为空,则调用 reset 会释放其原来所指的内存。而 release 则不会释放 unique_ptr 原来所指的内存。调用 release 会切断 unique_ptr 和它原来所指对象间的联系,如果我们不是用另一个智能指针来保存 release 返回的指针,则我们的程序就要负责资源的释放。
std::unique_ptr<int> p3(new int(4));
std::unique_ptr<int> p4(new int(5));
//auto ptr = p4.release(); // p4释放对象的控制权,返回对象内置指针
//p3.reset(ptr); // p3释放对象的内存,并用原p4的对象指针初始化并接管其内存
p3.reset(p4.release()); // p4释放控制权,返回对象指针; p3释放原有对象,接管p4的对象
传递 unique_ptr 参数和返回 unique_ptr
不能拷贝 unique_ptr 的规则有一个例外,可以拷贝或赋值一个将要销毁的 unique_ptr:从函数返回一个 unique_ptr;返回一个局部对象的拷贝。
unique_ptr<int> clone_1(int p){
return unique_ptr<int>(new int(p));
}
unique_ptr<int> clone_2(int p){
unique_ptr<int> ret(new int(p));
return ret;
}
这两段代码,编译器都知道要返回的对象将要被销毁。在此种情况下,编译器执行一种特殊的 “拷贝” (移动构造函数)。
删除器
类似 shared_ptr,unique_ptr 默认情况下用 delete 释放它指向的对象。同样的,我们可以重载一个 unique_ptr 中默认的删除器:
//p 指向一个类型为 objT 的对象,并使用一个类型为 delT 的对象 fnc 来释放 objT 对象
unique_ptr
同样以DataMem类来进行说明
class DataMem {
public:
//DataMem();
DataMem(size_t sz) : p(new int[10]) {}
//~DataMem() { release(); }
int* getPtr() { return p; }
void release() { delete[] p; p = nullptr; }
private:
int* p;
};
int *ptr = nullptr;
try {
//{
// DataMem dm = DataMem(10); // 局部对象,有析构函数
// ptr = dm.getPtr();
//
// //使用代码
// // ....
// throw 1;
//}
//{
// auto ReleaseMem = [](DataMem* dm)-> void {
// dm->release();
// };
// DataMem dm = DataMem(10); // 局部对象,无析构函数
// //std::shared_ptr pdm(&dm, [](DataMem* dm)->void {dm->release(); });
// std::unique_ptr pdm(&dm, ReleaseMem); //使用删除器必须指定模板类型
// //std::unique_ptr pdm(&dm, ReleaseMem); // 同上
// ptr = dm.getPtr();
// //使用代码
// // ....
// throw 1; // 假装有异常 , 局部对象无析构,shared_ptr接管并负责用删除器回收内存
// // ....
//}
{
std::unique_ptr<DataMem> pdm1(new DataMem(10));
// 移交指针控制权,指针初始化新的unique_ptr,并置空。
//std::unique_ptr pdm2 = std::move(pdm1);
std::unique_ptr<DataMem> pdm2(std::move(pdm1));
//类似如下
// std::unique_ptr pdm2;
// pdm2.reset(pdm1.release());
//可简写为 std::unique_ptr pdm2(pdm1.release());
}
}
catch (int e) {
std::cout << "catch throw " << e << std::endl;
}
catch (std::exception& e) {
std::cout << e.what() << std::endl;
}
weak_ptr 是一种不控制所指向对象生存周期的智能指针,它指向一个 shared_ptr 管理的对象。将一个 weak_ptr 绑定到一个 shared_ptr 不会改变 shared_ptr 的引用计数。一旦最后一个指向对象的 shared_ptr 被销毁,即便有 weak_ptr 指向对象,对象也还是会被释放的。
基本操作
weak_ptr
weak_ptr
w = p p 可以是一个 shared_ptr 或一个 weak_ptr。赋值后 w 与 p 共享对象
w.reset() 将 w 置为空
w.use_count() 与 w 共享对象的 shared_ptr 的数量
w.expired() 若 w.use_count 为 0,返回 true,否则返回 false
w.lock() 如果 w.expired() 为 true,返回一个空 shared_ptr,否则返回一个指向 w 的对象的 shared_ptr
基本使用方式
{
std::shared_ptr<std::string> sp(new std::string("hello, world!")); // 引用计数 1
//std::weak_ptr wp(sp); // wp也指向sp对象, 但不会增加引用计数, 仍为1
std::weak_ptr<std::string> wp = sp; // 同上
std::weak_ptr<std::string> wp2 = wp;
}
{
std::shared_ptr<std::string> sp = std::make_shared<std::string>("hello, world!"); // 引用计数
std::weak_ptr<std::string> wp(sp);
wp.reset(); // wp置为空, 不会对sp有任何影响, 引用计数不变换,仍为1
}
{
std::shared_ptr<std::string> sp = std::make_shared<std::string>("hello, world!"); // 引用计数
std::weak_ptr<std::string> wp(sp);
sp.reset(); // sp置为空, 引用计数为0,释放对象。 wp指向是已经释放的内存,不能直接访问
std::cout << wp.use_count() << std::endl; // 0
std::cout << wp.expired() << std::endl; // true <= use_count=0
std::shared_ptr<std::string> p = wp.lock(); // 为空指针
}
使用 weak_ptr 时,由于我们不确定其所指对象是否存在,要先调用 lock 函数检查一下其所指对象是否存在。
{ //weak_ptr是弱引用,指向对象可能不存在,不能直接访问对象
std::shared_ptr<std::string> sp1 = std::make_shared<std::string>("hello, world!");
std::weak_ptr<std::string> wp;
std::cout << wp.use_count() << std::endl; // 0
std::cout << wp.expired() << std::endl; // true <= use_count=0
wp = sp1;
std::cout << wp.use_count() << std::endl; // 1
std::cout << wp.expired() << std::endl; // false <= use_count!=0
std::shared_ptr<std::string> sp2(sp1);
std::cout << wp.use_count() << std::endl; // 2
std::cout << wp.expired() << std::endl; // false <= use_count!=0
sp2.reset(); // sp2置空, 但不影响sp1
// 通过 lock(), 若wp对象存在,返回一个共享对象的sp,否则返回一个空sp
if (std::shared_ptr<std::string> p = wp.lock()) {
std::cout << *p << std::endl; // 引用计数为2
}//作用于结束后引用计数减为1
}
利用 weak_ptr访问对象数据,不影响对象生命周期,同时防止操作对象已经不存在的情况
同样以StrBlob类为例,定义一个伴随指针类StrBlobPtr,保存一个wek_ptr指向StrBlob的data成员变量。通过使用weak_Ptr,不会影响一个指定的StrBlob所指向的的vector的生命周期,但是可以阻止用户访问一个已经不存在的vector的企图。
class StrBlobPtr; //前置声明
class StrBlob {
// 声明友元类,让StrBlobPtr对象可以访问StrBlob对象的私有变量或函数
friend class StrBlobPtr;
public:
using size_type = std::vector<std::string>::size_type;
StrBlob() = default;
StrBlob(std::initializer_list<std::string> il) :
data(std::make_shared<std::vector<std::string>>(il))
{
}
size_type size() const { return data->size(); }
bool empty() const { return data->empty(); }
void push_back(const std::string& str)
{
if (!data) {
data = std::make_shared<std::vector<std::string>>();
}
data->push_back(str);
}
void pop_back() { check(0, "pop_back on empty StrBlob"); data->pop_back(); }
// 这里没有修改成员对象的值,是调用成员的成员函数修改内部值
void pop_back() const { check(0, "pop_back on empty StrBlob"); data->pop_back(); }
// const函数, 建议返回const引用
const std::string& front() const { check(0, "front on empty StrBlob"); return data->front(); }
const std::string& back() const { check(0, "back on empty StrBlob"); return data->back(); }
// 实现必须放在 StrBlobPtr类实现之后,否则出现未定义,因为只有声明
StrBlobPtr begin();
StrBlobPtr end();
private:
std::shared_ptr<std::vector<std::string>> data;
void check(size_type i, const std::string& msg) const
{
if (i >= data->size()) throw std::out_of_range(msg);
}
};
class StrBlobPtr
{
public:
StrBlobPtr() : curr(0) {}
StrBlobPtr(StrBlob& a, std::size_t sz = 0) :
wptr(a.data), curr(sz) // StrBlobPtr为StrBlob友元类,可以直接访问私有数据
{}
//void push_back(const std::string& str) { check(0,"")->push_back(str); }
//void pop_back() const { check(0, "pop_back on empty StrBlob")->pop_back(); }
const函数, 建议返回const引用
//const std::string& front() const { return check(0, "front on empty StrBlob")->front(); }
//const std::string& back() const { return check(0, "back on empty StrBlob")->back(); }
// 解引用访问当前指向vector中的元素
std::string& deref() const
{
auto p = check(curr, "dereference past end");
return (*p)[curr];
}
// 前缀递增,返回递增后的对象的引用
StrBlobPtr& incr(){
check(curr, "increment past end of StrBlobPtr");
++curr;
return *this;
}
StrBlobPtr& operator++ () { // 调用方式为 ++StrBlobPtr
return incr();
}
StrBlobPtr& operator++ (int) { // 调用方式为 StrBlobPtr++
return incr();
}
friend bool eq(const StrBlobPtr &lhs, const StrBlobPtr &rhs);
private:
using SrtArr = std::vector<std::string>;
// 检查成功 返回一个底层数据的 shared_ptr
std::shared_ptr<SrtArr> check(std::size_t i, const std::string& msg) const
{
auto ret = wptr.lock(); // vector 是否被销毁?
if (!ret) throw std::runtime_error("unbound StrBlobPtr");
if (i >= ret->size()) throw std::out_of_range(msg);
return ret;
}
std::weak_ptr<SrtArr> wptr; // 保存一个weak_ptr, 意味着底层数据vector可能被销毁
std::size_t curr; // 在数组中的当前位置
};
StrBlobPtr StrBlob::begin(){
return StrBlobPtr(*this);
}
StrBlobPtr StrBlob::end(){
auto ret = StrBlobPtr(*this, data->size()); return ret;
}
// 该函数需要访问StrBlobPtr的私有变量,所以将其定义有友元函数
bool eq(const StrBlobPtr & lhs, const StrBlobPtr & rhs)
{
auto l = lhs.wptr.lock(), r = rhs.wptr.lock();
if (l == r)
return (!r || lhs.curr == rhs.curr);
else
return false;
}
bool neq(StrBlobPtr &lhs, StrBlobPtr &rhs)
{
return !eq(lhs, rhs);
}
测试的代码如下:
{
try {
配合 StrBlobPtr注释的函数代码使用
//{
// StrBlob sb;
// sb.push_back("abc");
// {
// StrBlobPtr sbp(sb);
// std::cout << sbp.front() << std::endl;
// sbp.push_back("123");
// }
// std::cout << sb.back() << std::endl;
//}
{
StrBlob sb;
sb.push_back("abc");
sb.push_back("123");
// 类似迭代器的使用
for (auto it = sb.begin(); it != sb.end(); ++it)
cout << it.deref() << endl;
}
}
catch (int e) {
std::cout << "catch throw " << e << std::endl;
}
catch (std::exception& e) {
std::cout << e.what() << std::endl;
}
}
标准库提供了一个可以管理new分配的数组unique_ptr版本,必须在对象类型后跟一对方括号。
{
std::unique_ptr<int[] > up(new int[4]{ 0,1,2,3 });
auto pi = up.get();
int arr1 = up[1]; //可以使用下标访问
up.reset(); //释放内存,自动调用 delete[]
}
shared_ptr未定义下标运算,智能指针也不支持算术运算, 因此访问数组元素必须用get获取内置指针,然后再用它来访问数组
{
std::shared_ptr<int> sp(new int[4]{ 0,1,2,3 });
auto pi = sp.get();
int arr1 = pi[1];//获取元素必须用内置指针进行数组元素访问
sp.reset(); //c++11的shared_ptr不支持管理动态数组,还需要指定删除器;c++14正常;
}
简单使用
{
std::allocator<std::string> strAlloc; // 创建一个string的分配器
std::string* p = strAlloc.allocate(10); // 分配10个为初始化的string
auto q = p;
strAlloc.construct(q++);
strAlloc.construct(q++, "123");
strAlloc.construct(q++, 10, '1'); // 10个'1'组成的字符串
// 执行对象"111111111"的析构,但是没有释放内存;; 只能对已经构造的元素进行操作;
//strAlloc.destroy(p+2);
while (q != p) {
strAlloc.destroy(--q);
}
strAlloc.deallocate(p, 10); // 释放内存, n必须为ps创建时的大小
}
{ //拷贝和填充未初始化内存的算法
std::vector<int> vi{ 0,1,2,3,4,5,6,7,8,9 }; // 10个元素的vector
std::allocator<int> alloc;
auto p = alloc.allocate(vi.size() * 2); //分配一个大小为20的未初始化的int内存
auto q = std::uninitialized_copy(vi.begin(), vi.end(), p); // vi初始化p, 返回最后指向(第11个未初始化int内存)
std::uninitialized_fill_n(q, vi.size(), 11); //剩下10个未初始化int内存的用11填充 //c4996 warning
alloc.deallocate(p, vi.size()*2 );
}