赞
踩
可编程内存基本上可分为以后三个大部分:
动态内存与智能指针:
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"};
b1 = b2;//从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<int> 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<int> 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<DataMem> 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<T> u1 空 unique_ptr,可以指向一个类型为 T 的对象。u1 会使用 delete 来释放它的
unique_ptr<T, D> u2 指针;u2 会使用一个类型为 D 的可调用对象来释放它的指针,初始化 u2 时
除了需要一个能转化成 T* 类型的对象外还需要一个 D 类型的对象来代替 delete
unique_ptr<T, D> u(d) 空 unique_ptr,指向类型为 T 的对象,用类型为 D 的对象 d 代替 delete
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<int>(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<objT, delT> p(new objT, fcn);
同样以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<DataMem> pdm(&dm, [](DataMem* dm)->void {dm->release(); }); // std::unique_ptr<DataMem, decltype(ReleaseMem)> pdm(&dm, ReleaseMem); //使用删除器必须指定模板类型 // //std::unique_ptr<DataMem, void (*)(DataMem* dm)> pdm(&dm, ReleaseMem); // 同上 // ptr = dm.getPtr(); // //使用代码 // // .... // throw 1; // 假装有异常 , 局部对象无析构,shared_ptr接管并负责用删除器回收内存 // // .... //} { std::unique_ptr<DataMem> pdm1(new DataMem(10)); // 移交指针控制权,指针初始化新的unique_ptr,并置空。 //std::unique_ptr<DataMem> pdm2 = std::move(pdm1); std::unique_ptr<DataMem> pdm2(std::move(pdm1)); //类似如下 // std::unique_ptr<DataMem> pdm2; // pdm2.reset(pdm1.release()); //可简写为 std::unique_ptr<DataMem> 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<T> w 空 weak_ptr 可以指向类型为 T 的对象
weak_ptr<T> w(sp) 与 shared_str sp 指向相同对象的 weak_ptr。T 必须能转化为 sp 指向的对象的类型
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<std::string> 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 ); }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。