12.1 动态内存和智能指针 | dynamic memory & smart pointer

12章之前的程序中使用的对象都有严格定义的生存期。

  • 全局对象在程序启动时分配,在程序结束时销毁。
  • 对于局部自动对象,当进入其定义所在的程序块时被创建,在离开块时销毁。
  • 局部static对象在第一次使用前分配,在程序结束时销毁。

除了自动和static对象,还支持动态分配对象。这些对象的生存期与创建位置无关,显式被释放时才会被销毁。

为了安全使用动态对象,标准库中有两个智能指针类型管理动态分配的对象。当一个对象应该被释放时,指向它的智能指针可以确保自动地释放它。

静态内存和栈内存

  • 静态内存用来保存局部static对象、类static数据成员以及定义在任何函数之外的变量。
  • 栈内存用来保存定义在函数内的非static对象。
  • 分配在静态或栈内存中的对象由编译器自动创建和销毁。栈对象仅在其定义的程序块运行时存在;static对象在使用前分配,程序结束时销毁。

除了静态内存和栈内存,每个程序还拥有一个内存池。这些内存称作自由空间(free store)堆(heap)

程序用堆来存储动态分配(dynamically allocated)对象,即在程序运行时分配的对象。动态对象的生存期由程序控制,因此在不需要时需要显式销毁。

动态内存和智能指针

C++中的动态内存管理是通过一堆运算符完成的:

  • new在动态内存中为对象分配空间并返回一个指向该对象的指针;
  • delete接受一个动态对象的指针,销毁该对象并释放关联的内存。

标准库中提供了两种智能指针(smart pointer)类型管理动态对象。智能指针的行为类似常规指针,重要的区别是它负责自动释放所指向的对象。新标准库提供的这两种智能指针的区别在于管理底层指针的方式:

  • shared_ptr允许多个指针指向同一个对象
  • unique_ptr“独占”指向的对象

标准库还有一个名为weak_ptr的伴随类,是一种弱引用,指向shared_ptr所管理的对象。

上述三种类型都定义在<memory>头文件中。

shared_ptr

类似vector,智能指针也是模板,因此创建一个智能指针时,必须给出指向的类型:

std::shared_ptr<int> p1;
std::shared_ptr<std::vector<int>> p2;

默认初始化的智能指针中保存着一个空指针。智能指针的使用方式类似普通指针,可以解引用返回对象。

shared_ptr和unique_ptr都支持的操作

expression -
shared_ptr<T> sp | unique_ptr<T> up 空智能指针
p 将p用作一个条件判断,若指向一个对象则true
*p 解引用
p->mem *p.mem
p.get() 返回p中保存的指针。注意是否已经释放了对象
swap(p, q)|p.swap(q)

shared_ptr独有操作

expression -
make_shared<T>(args) 返回一个shared_ptr,指向一个动态分配的类型为T的对象,使用args初始化该对象
shared_ptr<T>p(q) p是shared_ptr q的拷贝。该操作会递增q中的计数器,q中的指针必须可以转换为T*
p=q 二者都是shared_ptr且保存的指针必须可以相互转换。该操作会递减p的引用计数,递增q的引用计数。若p的引用计数变为0,则将其管理的原内存释放。
p.unique() p.use_count()为1(独占状态)则true,否则false
p.use_count() 返回与p共享对象的智能指针数量;主要用于调试

make_shared函数

make_shared是最安全的分配和使用动态内存的方法,避免了在定义后才初始化可能造成的错误。该函数同样定义在<memory>中:

int main(){
    std::shared_ptr<int> p1 = std::make_shared<int>(42);
    std::shared_ptr<std::vector<int>> p2 = std::make_shared<std::vector<int>>(10, 0);
    auto p3 = std::make_shared<std::map<std::string, int>>();
}

使用make_shared构造智能指针时可以使用auto方式。
若不传递任何参数,则会使用值初始化。
类似顺序容器的emplace,make_shared用其参数构造给定类型对象时传递的参数必须与string的某个构造函数相匹配。
可以使用make_shared和初始化列表

auto sp = std::make_shared<std::vector<int>>(std::initializer_list<int>({1,2,32}));
//或者
auto sp_map = std::make_shared<std::map<std::string, int>>();
*sp_map = {{"A", 0},{"B", 1}};

shared_ptr的拷贝和赋值

进行拷贝和赋值操作,每个shared_ptr都会记录有多少个其他shared_ptr指向相同对象:

auto p = make_shared<int>(42);
auto q(p);

每个shared_ptr都有一个关联的计数器,通常称为引用计数(reference count)。无论何时拷贝一个shared_ptr,计数器都会递增。

  • 将一个shared_ptr初始化另一个shared_ptr,或将其作为参数传给另一个函数以及作为函数的返回值,则它所关联的计数器就会递增
  • 当给shared_ptr一个新值或者被销毁(如局部shared_ptr)离开作用域,计数器会递减
  • 一旦一个shardd_ptr的计数器变为0,则会自动释放自己管理的对象。
int main(){
    int a = 0;
    int b = 1;

    auto sp3 = std::make_shared<int>(a);
    auto sp4 = std::make_shared<int>(b);
    std::cout<<sp3.use_count()<<'\t'<<sp4.use_count()<<std::endl;
    sp3 = sp4;  //指向同一对象(a),使得该对象引用计数+1
    std::cout<<sp3.use_count()<<'\t'<<sp4.use_count()<<std::endl;
    *sp4 = 1;   //改变对象a的值,引用计数不变
    std::cout<<sp3.use_count()<<'\t'<<sp4.use_count()<<std::endl;
    sp3 = std::make_shared<int>(b);    //令sp3指向其他对象,原对象a的引用计数-1
    std::cout<<sp3.use_count()<<'\t'<<sp4.use_count()<<std::endl;
}

shared_ptr自动销毁所管理的对象

当指向一个对象的最后一个shared_ptr被销毁(所指向对象的引用计数归0),shared_ptr类会自动调用该对象类的析构函数(destructor)销毁该对象

shared_ptr自动释放相关联的内存

动态对象不再使用时,shared_ptr会自动释放动态对象。

例如有一个函数返回shared_ptr,指向一个Foo类型的动态分配的对象:

struct Foo{
public:
    std::string s;
    int i;
    Foo(std::string str, int ii);
};

Foo::Foo(std::string str, int ii) {
    this->i = ii;
    this->s = str;
}

std::shared_ptr<Foo> foo(std::string s, int i){
    return std::make_shared<Foo>(s, i);
}

auto try_ptr(std::string s, int i, bool return_ptr = 0){
    auto p = foo(s, i);
    //离开该作用域,p指向的对象被自动释放
    //若存在返回,则指向对象的引用计数+1,因此不会被释放
    return return_ptr? p: nullptr;
}

int main(){
    std::string str = "A";
    int ii = 1;
    auto ptr = foo(str, ii);
    std::cout<<ptr->i<<'\t'<<ptr->s<<std::endl;
}

除此之外,shared_ptr会在无用之后依然保留的情况是,将shared_ptr放在一个容器中,之后重排了容器,从而不需要某些元素。这种情况下应该使用erase删除那些不必要的shared_ptr元素。

使用了动态生存期资源的类

程序使用动态内存的原因:

  1. 不知道自己需要多少对象
  2. 不知道对象的准确类型
  3. 需要在多个对象间共享数据

对于容器类,是由于第二种原因而使用的。

对于vector,拷贝一个vector时原有的vector和副本vector中的元素时相互分离的:

std::vector<int> v1;
{
    std::vector<int> v2 = {0, 1, 2};
    v1 = v2;
}
//此时离开作用域,v2及其中的元素被销毁但是v1拷贝的元素存在

假定类Blob会在不同对象的拷贝间共享相同的元素,则离开作用域时

Blob<int> b1
{
    Blob<int> b2 = {0, 1, 2};
    b1 = b2;
}
//离开作用域时需保证b2元素不能销毁

定义StrBlob类、构造函数、成员函数

class StrBlob{
public:
    typedef std::vector<std::string>::size_type size_type;
    //默认构造函数
    StrBlob();
    //以接受初始化列表的构造函数
    StrBlob(std::initializer_list<std::string> il);
    //基本方法
    size_type size() const {return data->size();};
    bool empty() const {return data->empty();};
    void push_back(const std::string &t) {
        std::cout<<"push_back"<<std::endl;
        data->push_back(t);};
    void pop_back();
    std::string& front();
    std::string& back();
    std::vector<std::string>::iterator begin();
    std::vector<std::string>::iterator end();

private:
    //使用shared_ptr管理装入的容器类型
    std::shared_ptr<std::vector<std::string>> data;
    //检查是否合法。data[i]不合法时抛出异常
    void check(size_type i, const std::string &msg) const;
};

//类外定义构造函数,类成员写在函数体之前、函数名的冒号之后

StrBlob::StrBlob(): data(std::make_shared<std::vector<std::string>>()) {};

StrBlob::StrBlob(std::initializer_list<std::string> il):
    data(std::make_shared<std::vector<std::string>>(il)) {};

void StrBlob::check(size_type i, const std::string &msg) const {
    if (i >= data->size()) throw std::out_of_range(msg);
}

//类外定义的成员方法

void StrBlob::pop_back() {
    check(0, "no element to pop");
    std::cout<<"pop_back"<<std::endl;
    data->pop_back();
}

std::string& StrBlob::front() {
    check(0, "no element in the front");
    std::cout<<"front"<<std::endl;
    return data->front();
}

std::string& StrBlob::back() {
    check(0, "no element in the back");
    std::cout<<"back"<<std::endl;
    return data->back();
}

std::vector<std::string>::iterator StrBlob::begin() {
    check(0, "no element");
    return data->begin();
}

std::vector<std::string>::iterator StrBlob::end() {
    check(0, "no element");
    return data->end();
}

int main(){
    StrBlob s1 = {"a", "b", "c"};
    s1.pop_back();
    //使用范围for必须实现begin和end
    s1.push_back("d");
    std::ostream_iterator<std::string> a(std::cout, " ");
    for (auto j: s1)
        a = j;
}

直接管理内存

使用new动态分配和初始化对象

自由空间分配的内存是无名的,因此new无法为其分配的对象命名,而是返回一个指向该对象的指针

int* pi = new int;

内置类型和组合类型通常被默认初始化,因此值是ub。类类型使用默认构造函数初始化

string* ps = new string;
int* pi = new int;

可以使用直接初始化、列表初始化等构造方式动态分配对象。

int* p = new int(10);
std::string s = new string(10, '!');
auto v = new std::vector<int>{0, 1, 2, 3, 5};

也可以使用值初始化,加括号即可:

int* p1 = new int;          //默认初始化
int* p2 = new int();        //值初始化
std::cout<<*p1<<std::endl;  //-842150451
std::cout<<*p2<<std::endl;  //0

若提供了括号包围的初始化器,则可以使用auto。注意当括号中只有单一初始化器才可以使用auto。

动态分配const对象

可以使用new动态分配一个const对象。

int main(){
    int i = 1024;
    const auto* p = new int(i);
    std::cout<<typeid(*p).name()<<'\t'<<*p<<std::endl;
    //int     1024
}

内存耗尽

当某个程序用尽可用内存,new会失败。默认情况下若new不出所需内存空间则会抛出类型为bad_alloc的异常。可以改变使用new的方式阻止其抛出异常:

#include <new>
int* p1 = new int;
int* p2 - new(nothrow) int 
//分配失败时不throw而是返回一个空指针

这种形式的new称为定位new(placement new),允许向new传递额外的参数。如此例中的nothrow。

bad_alloc和nothrow都定义在头文件<new>中。

释放动态内存

为了防止内存耗尽,通过delete表达式(delete expression)将动态内存归还给系统。delete接受一个指针,指向想要释放的对象。
与new类似,该表达式执行两个动作:销毁给定的指针指向的对象;释放对象对应的内存。

指针值和delete

传递给delete的指针必须指向动态分配的内存,或是一个空指针。释放一块并非new分配的内存或将相同的指针值多次释放是UB。
释放一个空指针总是不会发生错误。

int main(){
    int i = 0, *p = &i, *pn = nullptr;
    delete p;  //报错。不是动态分配的地址
    delete pn; //总是不报错
}

动态对象的生存期直到被释放时为止

shared_ptr管理的内存在销毁时被自动释放。但对于内置指针管理的内存,在显式释放之前都是存在的。就算离开作用域,该处的内存依然未被释放。

int main() {
    int ** addr;
    int* p = new int;
    {
        int* ps = new(std::nothrow) int(100);
        p = ps;
        addr = &ps;
    }
    std::cout<<*p<<**addr<<std::endl;  //原地址存放的值依然存在
}

delete之后则上述*p**addr均变为ub:

int main() {
    int ** addr;
    int* p = new int;
    {
        int* ps = new(std::nothrow) int(100);
        p = ps;
        addr = &ps;
        delete ps;
    }
    std::cout<<*p<<**addr<<std::endl;  //两个ub值
}

delete之后重置指针值(仅提供有限的保护)

当delete一个指针后,指针值变为无效,但指针依然保存着被释放的动态内存地址。delete之后该指针成为了空悬指针(dangling pointer),即指向一块曾保存对象而已经无效的内存的指针。

空悬指针具有未初始化指针的所有缺点。

避免方式为:在指针即将离开其作用域之前释放掉所关联的内存。若需要保留指针本身,则需在delete之后将nullptr赋予指针。

但是如此操作仅对该指针有效。若存在多个指针指向一块内存地址的情况,则对其他指针无效。例如:

int* p = new int(0);
auto q = p;
delete p;
p = nullptr;
//此时q依然是空悬指针

结合使用shared_ptr和new

不初始化的智能指针是一个空指针。
此外,可以使用new返回的指针来初始化智能指针。

接受指针参数的智能指针构造函数是explicit的,因此需要直接初始化。不能直接将内置指针转化为智能指针:

shared_ptr<int> p1 = new int(100);  //错误
shared_ptr<int> p2(new int(100));   //正确:直接初始化

也可以使用make_shared(相当于仅仅用了new出的指针的对象本身而不是这个指针),注意类型:

int* p = new int(42);
auto a = std::make_shared<int>(*p);   //只是利用了*p的值
std::shared_ptr<int> b(p);

另外,若要返回一个shared_ptr,在return语句中的正确写法为:

return shared_ptr<int>(new int(p))

而不是简单的return new int(p)

定义和改变shared_ptr的其他方法

- -
shared_ptr<T> p(q) p管理内置指针q指向的对象;q必须指向new分配到内存并且能转换为T*类型
shared_ptr<T> p(u) p从unique_ptr u处接管了对象所有权:将u置空
shared)ptr<T> p(q, d) p接管了内置指针q指向的对象的所有权,q必须能转换为T*类型。p将使用可调用对象d代替delete
shared_ptr<T> p(p2, d) p是shared_ptr p2的拷贝,使用可调用对象d代替delete
p.reset() | p.reset(q) | p.reset(q, d) 若p是唯一指向其对象的shared_ptr,释放此对象。若传递了q,则令p指向q,否则置空。若传递了d,则会调用可调用对象d而不是delete。

不要混合使用智能指针和普通指针

shared_ptr可以协调对象的析构,但仅限于自身的拷贝之间。因此推荐使用make_shared而不是new。这样就能在分配对象的同时将shared_ptr与之绑定,避免无意中将同一块内存绑定到多个独立创建的shared_ptr上。

考虑如下函数:

void process<shared_ptr<T> ptr>{
    //do something
};  //离开作用域即被销毁

该函数的传参属于传值方式,实参被拷贝到ptr中,会增加引用计数。若传递给该函数一个shared_ptr:

shared_ptr<T> p;  //引用计数为1
precess(p);       //在拷贝传参时,引用计数+1,为2
auto i = *p;      //正确,p的引用计数为1

若传递一个由内置指针转化了的shared_ptr:

T* x(new T());
process(x);                  //错误,参数类型不一致
process(shared_ptr<T>(x));   //正确,但括号内语句作为临时指针,在该表达式结束后就被销毁
auto j = *x;      //错误:x已经是空悬指针

将一个shared_ptr绑定到一个普通指针时,内存的管理责任已经属于该shared_ptr,一旦这样做了就不应该再使用内置指针访问该地址。

不要使用get初始化另一个智能指针或为智能指针赋值

智能指针类型定义了名为get的函数,返回一个内置指针,该内置指针指向智能指针指向的对象。

  • get的设计情况是:需要向不能使用智能指针的代码传递一个内置指针。也就是将指针的访问权限传递给代码

  • 使用get返回的指针的代码不能delete该指针。因此只有在确定代码不delete指针的情况下才能使用get。

  • 编译器不会报错,但不能将另一个智能指针也绑定到get返回的这个内置指针。永远不要用get初始化另一个智能指针或为另一个智能指针赋值

其他shared_ptr操作

可以使用reset将新的指针赋予一个shared_ptr。与赋值类似,会更新引用计数。

reset常和unique一起使用来控制多个shared_ptr共享的对象。在改变底层对象之前检查自己是否是当前对象仅有的用户。若不是,则制作一份新的拷贝。

if(!p.unique()) p.reset(new string(*p)); //用对象的值分配新的拷贝,而不是指向原来的对象
*p += newVal;  //拷贝后改变对象的值

另外,reset不接受智能指针作为参数,因此下列操作非法:

b.reset(std::make_shared<int>(*b));
b.reset(std::shared_ptr<int>(*b));

智能指针和异常

异常处理程序能在异常发生后零程序继续,而该类程序需要确保在异常发生后资源能被正确释放。一个简单的确保资源正常释放的方法就是使用智能指针。

函数退出的两种可能:正常处理结束、发生异常。两种情况都会销毁局部对象。

如果使用智能指针,即使程序块过早结束也能确保在内存不再需要时将其释放:

void foo(){
    shared_ptr<int> sp(new int(42)); 
    //假设该处抛出一个未捕获的异常
}//函数结束后自动释放内存

与之相对,发生异常时直接管理的静态内存即使发生异常也不会在delete之前不会自动释放。

智能指针和哑类

析构函数负责清理对象使用的资源。但是并非所有类都良好定义了析构函数,因此需要用户显式释放使用的资源。若在资源分配和释放之间发生异常,则程序会发生资源泄漏。

对于没有析构函数的类,使用智能指针相当有效。

使用自己的释放操作

在shared_ptr销毁时默认对管理的指针进行delete操作,但可以定义一个删除器(deleter)代替默认的delete操作。例如:

void end_connection(connection* p){disconnect(*p);}

该函数接受一个connection类的指针,来进行指定的释放操作。
在创建shared_ptr时即可传递一个指向删除器函数的参数:

void f(destination& d){
    connection c = connect(&d);
    shared_ptr<connection> p (&c, end_connection);
    //使用connection类
    //即使程序异常也能正常释放
}

unique_ptr

unique_ptr“拥有”其指向的对象,当其被销毁时,指向的对象也被销毁。

不同于shared_ptr,unique_ptr没有类似make_shared的函数返回一个unique_ptr。因此定义时只能通过绑定一个new出的指针上。

类似shared_ptr,使用new返回的指针进行初始化只能采用直接初始化。

由于独占指向对象,unique_ptr不支持普通的拷贝和赋值操作。

但是可以通过release或reset将指针的所有权从一个非const的unique_ptr转移给另一个unique_ptr:

int main(){
    std::unique_ptr<int> p1(new int(1));
    //将所有权从p1转给p2的同时p1置空
    std::unique_ptr<int> p2(p1.release());
    std::cout<<*p2<<std::endl;        //1
    std::unique_ptr<int> p3(new int(2));
    //将所有权从p3转给p2并释放p2原来指向的内存
    p2.reset(p3.release());
    std::cout<<*p2<<std::endl;        //2
}

总之,对于unique_ptr,要交出所有权的一方作为参数均需要.release()。调用release会切断unique_ptr和其原来管理的对象之间的联系。release返回的指针通常被用来初始化另一个指针或者为另一个指针赋值。

- -
unique_ptr<T> u1 | unique_ptr<T, D> u2 空unique_ptr,可以指向类型T的对象。u1使用delete释放它的指针;u2使用类型D的可调用对象释放他的指针。
unique_ptr<T, D> u(d) 空unique_ptr,指向类型为T的对象,用类型为D的对象d代替delete
u = nullptr 释放u指向的对象
u.release() u放弃控制权并置空,返回指向原对象的(内置)指针
u.reset() | u.reset(q) | u.reset(nullptr) 若提供了内置指针则指向该对象,否则置空

传递unique_ptr参数和返回unique_ptr

不能拷贝unique_ptr的规则有一个例外:可以拷贝一个将要被销毁的unique_ptr。最常见的例子是从函数返回一个unique_ptr:

unique_ptr<int> clone(int p){
    return unique_ptr<int>(new int(p));
}

或返回一个局部对象的拷贝:

unique_ptr<int> clone(int p){
    unique_ptr<int> ret(new int(p));
    return ret;
}

这是一种特殊“拷贝”。在13.6.2节详述。

向unique_ptr传递删除器

重载unique_ptr中的默认删除器和shared_ptr的机制不同。

重载unique_ptr的删除器会影响到unique_ptr的类型和构造,必须在尖括号中指名删除器类型(shared_ptr仅影响构造,尖括号内类型只有一个)。

weak_jptr

  • weak_ptr不控制所指向对象生存期,它指向一个由shared_ptr管理的对象。
  • 将一个weak_ptr绑定到一个shared_ptr不会改变其引用次数。
  • 一旦最后一个指向对象的shared_ptr被销毁,对象就被释放,无论是否有weak_ptr。

因此,名称符合其weak的特点

expression -
weak_ptr<T> w 空weak_ptr可以指向类型为T的对象
weak_ptr<T> w(sp) 与shared_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() 如果expired为true则返回空shared_ptr,否则返回指向w的对象的shared_ptr

创建一个weak_ptr需要用一个shared_ptr进行初始化:

auto p = make_shared<int>(42);
weak_ptr<int> wp(p);

由于weak_ptr的对象可能不存在,不能直接使用其访问对象,而是利用lock检查指向的对象是否依然存在。如果返回的shared_ptr存在,则其指向的底层对象也一直存在。

核查指针类

尝试为StrBlob定义一个伴随指针类StrBLobPtr,该类会保存一个weak_ptr指向StrBlob的data成员,这是初始化时提供的。通过使用weak_ptr不会影响给定的StrBlob的生存期,但是可以阻止用户访问一个不存在的vector。

含有运算符重载、shared_ptr、weak_ptr的“完全体”StrBlob和StrBlobPtr类:

#include <vector>
#include <iostream>
#include <memory>
#include <iterator>
#include <algorithm>
#include <string>


class StrBlobPtr;
class StrBlob{
public:
    friend class StrBlobPtr;
    typedef std::vector<std::string>::size_type size_type;
    //默认构造函数
    StrBlob();
    //以接受初始化列表的构造函数
    StrBlob(std::initializer_list<std::string> il);
    //基本方法
    [[nodiscard]] size_type size() const {return data->size();};
    [[nodiscard]] bool empty() const {return data->empty();};
    void push_back(const std::string &t) {
        std::cout<<"push_back"<<std::endl;
        data->push_back(t);};
    void pop_back();
    std::string& front();
    std::string& back();
    StrBlobPtr begin();
    StrBlobPtr end();

private:
    //使用shared_ptr管理装入的容器类型
    std::shared_ptr<std::vector<std::string>> data;
    //检查是否合法。data[i]不合法时抛出异常
    void check(size_type i, const std::string &msg) const;
};

//类外定义构造函数,类成员写在函数体之前、函数名的冒号之后

StrBlob::StrBlob(): data(std::make_shared<std::vector<std::string>>()) {};

StrBlob::StrBlob(std::initializer_list<std::string> il):
        data(std::make_shared<std::vector<std::string>>(il)) {};

void StrBlob::check(size_type i, const std::string &msg) const {
    if (i >= data->size()) throw std::out_of_range(msg);
}

//类外定义的成员方法

void StrBlob::pop_back() {
    check(0, "no element to pop");
    std::cout<<"pop_back"<<std::endl;
    data->pop_back();
}

std::string& StrBlob::front() {
    check(0, "no element in the front");
    std::cout<<"front"<<std::endl;
    return data->front();
}

std::string& StrBlob::back() {
    check(0, "no element in the back");
    std::cout<<"back"<<std::endl;
    return data->back();
}


class StrBlobPtr{
public:
    StrBlobPtr(): curr(0) {};
    StrBlobPtr(StrBlob &a, size_t sz = 0): wptr(a.data), curr(sz) {};
    std::string& operator*() const;
    StrBlobPtr& operator++();
    bool operator==(StrBlobPtr&) const;
    bool operator!=(StrBlobPtr&) const;

private:
    [[nodiscard]]std::shared_ptr<std::vector<std::string>> check (std::size_t, const std::string&) const;
    std::weak_ptr<std::vector<std::string>> wptr;
    std::size_t curr;
};

std::shared_ptr<std::vector<std::string>> StrBlobPtr::check(std::size_t i, const std::string& s) const {
    auto ret = wptr.lock();
    if(!ret) throw std::runtime_error("unbound StrBlobPtr");
    if(i >= ret->size()) throw std::out_of_range(s);
    return ret;
}

//重载运算符方式
std::string& StrBlobPtr::operator*() const {
    auto p = check(curr, "dereference past end");   //是lock返回的指针
    return (*p)[curr];
}

StrBlobPtr& StrBlobPtr::operator++() {
    check(curr, "increment past end of StrBlobPtr");  //已经是尾后则不可递增
    ++curr;
    return *this;
}

StrBlobPtr StrBlob::begin() {
    check(0, "no element");
    return StrBlobPtr(*this);
}

StrBlobPtr StrBlob::end() {
    check(0, "no element");
    auto ret = StrBlobPtr(*this, data->size());
    return ret;
}

bool StrBlobPtr::operator==(StrBlobPtr& p) const{
    return this->curr == p.curr? true : false;
}

bool StrBlobPtr::operator!=(StrBlobPtr& p) const{
    return this->curr == p.curr? false : true;
}


int main(){
    StrBlob s1 = {"aa", "bb", "cc"};
    s1.pop_back();
    //使用范围for必须实现begin和end成员
    s1.push_back("dd");

    auto sp = StrBlobPtr(s1, 0);
    //注意#include<string>
    std::cout<<*sp<<std::endl;
    ++sp;
    std::cout<<*sp<<std::endl;

    auto iter = s1.begin();
    //for(iter, s1.end(); iter != s1.end(); ++iter) std::cout<<*iter;
    //若使用范围for需要重载指针的!=以及++
    for(auto& j: s1) std::cout<<j<<" ";
    std::cout<<std::endl;
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,921评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,635评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,393评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,836评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,833评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,685评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,043评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,694评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,671评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,670评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,779评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,424评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,027评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,984评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,214评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,108评论 2 351
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,517评论 2 343

推荐阅读更多精彩内容