• C++ Primer学习笔记


    12.1 动态内存与智能指针

    new, delete运算符:动态申请,释放内存;
    智能指针:shared_ptr,unique_ptr,weak_ptr,由标准库提供,头文件 memory

    12.1.1 shared_ptr类

    允许多个指针指向同一个对象。默认初始化的shared_ptr指针为空指针,使用方式同普通指针,解引用返回所指向的对象。

    // shared_ptr 定义
    shared_ptr<string> p1; // p1 指向string
    shared_ptr<list<int>> p2; // p2 指向list<int>
    
    // 智能指针的使用,解引用以及检测是否为空
    if (p1 && p1->empty()) {// 如果指针p1非空,并且指向一个空string
      *p = "hello";  // 将一个新值赋予string
    }
    
    // 默认初始化的shared_ptr指针为空指针,验证方式
    shared_ptr<string> pps;
    if (!pps) cout << "shared_ptr is null" << endl; // 打印 "shared_ptr is null"
    else cout << "shared_ptr is not null" << endl; 
    

    shared_ptr重要操作

    swap(p,q); // 交换p和q的指针
    p.swap(q);
    
    make_shared<T>(args); // 返回一个shared_ptr指针,指向一个动态分配的类型为T的对象。args用于初始化改对象
    shared_ptr<T> p(q); // p拷贝构造q,q中计数器+1。要求q是shared_ptr,且q的指针能转化成T* 
    p = q; // p,q都是shared_ptr指针。p计数-1,q计数+1.p计数为0时,p指向内存释放
    
    p.unique(); // 若p.use_count() == 1,返回true,否则返回false
    p.use_count(); // 与p共享对象的智能指针计数。可能速度慢,主要用于调试
    

    make_shared()函数
    标准库函数make_shared,是安全分配和使用动态内存的方法。

    // make_shared使用方法
    // p3指向值为42的int
    shared_ptr<int> p3 = make_shared<int>(42);
    
    // p4指向值为"9999999999" (10个'9')
    shared_ptr<string> p4 = make_shared<string>(10, '9');
    
    // p5指向值为0的int
    shared_ptr<int> p5 = make_shared<int>();
    

    shared_ptr拷贝和赋值
    拷贝shared_ptr时,与其关联的引用计数(reference count)会递增。shared_ptr赋予新值,或离开作用域被销毁时,计数器递减。
    一旦shared_ptr计数值为0,它会自动释放自己所管理的对象。

    auto r = make_shared<int>(42); // r指向的int只有一个引用者
    r = q; // r赋值,指向另外一个地址,引用计数-1。q引用计数+1。r原来指向的对象没有引用者,自动释放
    

    shared_ptr自动销毁、释放关联内存
    shared_ptr销毁对象具体工作,通过析构函数(destructor)来完成。
    shared_ptr在离开作用域时,会自动释放关联内存,依据是引用计数是否为0。

    shared_ptr<Foo> factory(T arg) {
      return make_shared<Foo>(arg);
    }
    
    // 版本1:p离开作用域,引用计数-1 == 0,自动释放p所指向内存
    void use_factory(T arg) {
      shared_ptr<Foo> p = factory(arg);
    }
    
    // 版本2:p离开作用域,但返回p时,会拷贝到一个临时缓存,引用计数-1+1 != 0,p所指向内存不会释放
    shared_ptr<Foo> use_factory(T arg) {
      shared_ptr<Foo> p = factory(arg);
      return p;
    }
    

    使用了动态生存期的资源的类
    程序使用动态内存通常原因:

    1. 不知道自己需要使用多少对象;
    2. 不知道所需对象的准确类型,如在使用基类指针的时候,不知道具体使用的哪个派生类,还是基类自身;
    3. 需要多个对象间共享数据;

    12.1.2 直接管理内存

    使用2个运算符:new,delete来分配、释放动态内存。

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

    // new基本类型
    int *p1 = new int; // p1指向未初始化无名对象(随机值)
    int *p2 = new int(); // p1指向初值为0的int
    int *p3 = new int(100); // p1 指向初值为100的int
    // new容器类型
    string *ps = new string; // ps指向空string
    string *ps2 = new string(10, '9'); // 指向"9999999999"
    vector<int> *pv = new vector<int>{0,1,2,3,4,5,6,7,8,9}; // pv指向vector<int>,元素序列为花括号内容
    
    // new自动推断类型
    auto p1 = new auto(obj); // p1指向与obj同类型对象
    
    auto p2 = new auto{a,b,c}; // 错误:括号中只能有单个初始化器
    
    // new const对象
    const int* pci = new const int(100); // p1指向const int,值为100
    const string* pcs = new const string; // p1指向const string,值为空
    

    // 内存耗尽,抛出bad_alloc异常。bad__alloc和nothrow头文件new
    // 分配失败,new 返回空指针
    int *p1 = new int; // 如果分配失败,抛出std::bad_alloc异常
    // 定位new,允许向new传递额外参数
    int *p2 = new (nothrow) int; // 如果分配失败,返回空指针。(nothrow)对象强制不抛出异常


    delete释放动态内存
    delete用于释放new出来的动态内存。传递给delete的指针必须指向动态分配的内存,或者空指针。
    如果释放一块非new分配的内存,或者将相同的指针值释放多次,行为未定义。

    delete p; // p必须指向一个动态分配的对象或是一个空指针
    
    // delete和指针使用
    int i = 0, *pi1 = &i, *pi2 = nullptr;
    double *pd = new double(33);
    delete i; // 错误:因为i不是指针
    delete pi1; // 错误:pi1指向局部变量i
    delete pi2; // 正确
    delete pi2; // 正确:多次释放空指针没有问题
    
    delete pd; // 正确
    delete pd; // 错误:pd所指向内存已释放
    
    // delete const对象
    const int *pci = new const int(100);
    delete pci; // 正确:不能修改const值,但能释放const对象
    

    动态内存关联存在的问题

    1. 忘记delete内存。很难排查。
    2. 使用已经释放的对象。通过在释放内存后,将指针直为空。
    3. 同一块内存释放两次。

    建议:尽量使用智能指针(shared_ptr等),可以有效避免new、delete管理动态内存带来的这3个问题。
    如果要使用new、delete,delete释放动态内存后指针称为空悬指针(dangling pointer),建议将空悬指针置为nullptr。

    delete后重置指针的方法,只对该指针有效,对其他扔指向已释放的内存的指针无效。

    int *p(new int(42));
    auto q = p;
    delete p; // 释放了p所指内存,p和q都无效
    p = nullptr; // 重置p,对q没有影响
    

    12.1.3 shared_ptr和new结合使用

    智能指针默认初始化为空。可以让智能指针指向new出来的指针。
    接受指针参数的构造函数是explicit的,也就是说,不能将一个内置指针隐式转化成智能指针,必须使用直接初始化形式来初始化智能指针,而不能用拷贝初始化。

    默认情况下,一个用来初始化智能指针的普通指针必须指向动态内存,因为智能指针默认使用delete释放关联对象。如果智能指针绑定到其他类型指针上,必须提供自己的操作来替代delete。

    shared_ptr<double> p1; // p1 为空指针
    shared_ptr<int> p2(new int(42)); // p2指向一个值为42的int
    
    shared_ptr<int> p1 = new int(100); // 错误:智能指针必须使用直接初始化
    shared_ptr<int> p2 (new int(100)); // 正确
    
    shared_ptr<int> clone(int p) {
      return new int(p); // 错误:隐式转化成shared_ptr<int>
    }
    
    shared_ptr<int> clone(int p) {
      return shared_ptr(new int(p)); // 正确:显示转化成shared_ptr<int>
    }
    

    定义和改变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
    
    // 若p是唯一指向其对象的shared_ptr,reset会释放此对象。如果传递了指针q,则p指向q,否则p置为空。如果传递了参数d,将会调用d而非delete。
    p.reset(); 
    p.reset(q);
    p.reset(q, d);
    

    不要混合使用普通指针和智能指针...
    推荐使用make_shared,不推荐使用new,因为shared_ptr可以协调对象的析构,但仅限于自身的拷贝(shared_ptr之间)。这样能在分配对象的同时将shared_ptr与之绑定,从而避免无意中将同一块内存绑定到多个独立创建的shared_ptr上。
    如果将普通指针转换成智能指针,作为参数传递给函数后,可能造成普通指针指向的内存通过智能指针释放,而转变成空悬指针。

    void process(shared_ptr<int> ptr) {
      // use ptr
    }// 离开作用域,ptr计数-1,ptr及指向内存被销毁
    
    // 正确使用方法:不要混合使用普通指针和智能指针,注意并不是不能混合使用new和shared_ptr
    shared_ptr<int> p(new int(42)); // 引用计数为1
    process(p); // process中p引用计数为2
    int i = *p; // 正确:引用计数为1
    
    
    // 错误使用方法:智能指针引用计数为0 会导致普通指针变成空悬指针
    int* x(new int(42);
    process(x); // 错误:不能将int*转换成shared_ptr<int>,也就是实参类型与形参类型不匹配
    process(shared_ptr<int>(x)); // 合法,但内存会被释放
    int j = *x; // 未定义行为:因为x已经成为空悬指针
    

    ...也不要使用get初始化另一个智能指针或为智能指针赋值
    因为通过get,让另外智能指针指向原来的内存区域,当该智能指针(独立的)引用计数为0时,会释放指向的内存,从而导致原来的智能指针称为空悬指针。而对空悬指针的解引用访问,会造成未定义行为。

    shared_ptr<int> p(new int(42)); // 引用计数为1
    int *q = p.get(); // 正确:但用q的时候,不要让它管理的指针被释放
    {// 新程序块
      // 未定义:两个独立的shared_ptr指向相同内存
      shared_ptr<int> (q);
    }// 程序块结束,q被销毁,它指向的内存被释放
    
    int foo = *p; // 未定义:p指向的内存已经被释放了
    

    其他shared_ptr的操作
    用reset将一个新指针赋予一个shared_ptr,reset与赋值类似,会更新引用计数。reset经常搭配unique一起使用

    shared_ptr<int> p;
    
    p = new int(1024); // 错误;类型不兼容,不能将一个指针赋予shared_ptr
    p.reset(new int(1024); // 正确:p指向一个新对象
    
    // reset搭配unique使用
    unique_ptr<string> p("hello");
    if (!p.unique()) p.reset(new string(*p)); // p不是唯一用户,分配新拷贝
    
    *p += newVal; // reset之后,p是唯一的用户,可以改变对象的值
    

    12.1.4 智能指针和异常

    智能指针管理的内存,即使程序由于发生异常过早结束,智能指针类也能确保内存不需要时释放。而new出来的普通指针,如果在手动delete之前,是不会自动释放内存的。

    // 函数结束时,shared_ptr自动释放内存
    void f() {
      shared_ptr<int> sp(new int(42)); // 分配一个新对象
      // 发生异常,f中未捕获
    }
    
    // 函数结束,delete不能正常是否内存
    void f() {
      int *ip = new int(42); // 动态分配一个新对象
      //发生异常,f中未捕获
      delete ip; // 无法正常执行delete释放内存
    }
    

    智能指针和哑类
    可以用智能指针,来释放没有定义析构函数、又分配了资源的类。因为程序员很可能会忘记释放资源。

    // 使用connection类进行网络编程,经常忘记disconnect释放资源
    struct destination;
    struct connection; // connection没有析构函数,需要用disconnect释放资源
    connection connect(destination*); // 打开连接
    void disconnect(connection); // 关闭指定连接
    
    // 错误范例:忘记disconnect,导致资源没能正常释放
    void f(desitnation &d) {
      // 获得一个连接:记住使用完后要关闭它
      connection c = connect(&d);
      // 使用连接connection对象
      // 如果f退出前忘记调用disconnect,就无法关闭c
    }
    
    // 通过shared_ptr & 释放操作来实现自动释放资源
    void f(destination &d) {
      connection c = connect(&d);
      shared_ptr<connection> p(&c, end_connection); // end_connection调用disconnect,这样就能确保连接被关闭了
      // 使用连接
      // 当f退出时(即使由于异常退出),connection会被正确关闭。这里end_connection是p的删除器
    }
    

    智能指针基本规范:

    • 不使用相同的内置指针值初始化/reset多个智能指针; -- 可以用一个智能指针初始化另外一个智能指针
    • 不delete get()返回的指针; -- get返回的指针是普通指针,混用普通指针和智能指针可能导致未定义行为
    • 不使用get()初始化或reset另一个智能指针;-- 同上一点,get返回的是普通指针,同时get原来的指针是一个智能指针,这样会让两个独立的智能指针指向同一块内存。reset也是一样的道理
    • 如果使用get()返回的指针,记住当最后一个对应的智能指针销毁后,指针就变成无效了; -- get返回的普通指针和智能指针指向同一块内存
    • 如果使用智能指针管理的资源不是new分配的内存,记住传递给它一个删除器;

    12.1.5 unique_ptr

    一个unique_ptr“拥有”它所指向的对象。
    与shared_ptr不同:

    1. 某个时刻只能有一个unique_ptr指向一个给定对象。当unique_ptr被销毁时,它所指向的对象也被销毁;
    2. 没有类似make_shared的标准库函数返回一个unique_ptr,定义一个unique_ptr时,需要将其绑定到一个new返回的指针上;
    // unique_ptr支持直接初始化
    unique_ptr<double> p1; // p1是可以指向double的unique_ptr
    unique_ptr<int> p2(new int(42)); // p2指向值为42的int
    
    // unique_ptr不支持普通拷贝或复制
    unique_ptr<string> p1(new string("Stegosarus")); // 正确
    unique_ptr<string> p2(p1); // 错误:unique_ptr不支持拷贝
    unique_ptr<string> p3; // 正确
    p3 = p2; // 错误:unique_ptr不支持赋值
    

    unique_ptr操作*

    // 空unique_ptr,可以指向T类型对象。u1使用delete来释放指针;u2使用类型为D的可调用对象来释放指针
    unique_ptr<T> u1; 
    unique_ptr<T, D> u2;
    unique_ptr<T, D> u(d); // 使用类型为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了?
    可以通过release或reset。

    // 将所有权从p1(指向string Stegosaurus)转移给p2
    unique_ptr<string> p2(p1.release()); // release将p1置为空
    
    unique_ptr<string> p3(new string("Trex"));
    // 将所有权从p3转移给p2
    p2.reset(p3.release()); // reset释放了p2原来指向的内存
    
    // 错误:没有保存release返回的指针,程序就要负责资源的释放
    p2.release();
    
    // 正确:但必须记得delete(p)
    auto p = p2.release(); 
    

    传递unique_ptr参数和返回unique_ptr*
    不能拷贝unique_ptr规则有一个例外:可以拷贝或赋值一个将要被销毁的unique_ptr。最常见的是从函数返回一个unique_ptr。

    下面这两段代码,编译器都知道要返回的对象将要被销毁,此时,编译器执行一种特殊的“拷贝”。

    unique<int> clone(int p) {
      return unique<int>(new int(p));
    }
    
    // 还可返回一个局部对象的拷贝
    unique<int> clone(int p) {
      unique_ptr<int> ret(new int(p));
      // ...
      return ret;
    }
    

    向后兼容:auto_ptr
    标准库的早起版本包含一个名为auto_ptr的类,具有unique_ptr部分特性,但不是全部。不能在容器中保存auto_ptr,也不能从函数中返回auto_ptr。虽然auto_ptr是标准库的一部分,但编写程序时应该使用unique_ptr。

    向unique_ptr传递删除器*
    shared_ptr, unique_ptr默认用delete释放所指向的对象。类似地,可以重载一个unique_ptr中默认的删除器,不过必须值尖括号中unique_ptr指向类型之后提供删除器类型。在创建或reset一个unique_ptr类型的对象时,必须提供一个指定类型的可调用对象(删除器):

    unique_ptr<objT, delT> p(new objT, fcn); // p 指向类型为objT的对象,并使用一个类型为delT的对象释放objT对象,p销毁时会调用名为fcn的delT类型对象
    
    // 用unique_ptr代替shared_ptr
    void f(destination &d) {
      connection c = connect(&d);
      unique_ptr<connection, decltype(end_connection) *> p(&c, end_connection); // p销毁时,连接将会关闭
      // 使用连接
      // f退出时(不论是否由于异常而退出),connection会被正确关闭
    }
    

    12.1.6 weak_ptr

    weak_ptr是一种不控制所指向对象生存期的智能指针,指向一个由shared_ptr管理的对象。weak_ptr绑定到shared_ptr不会改变引用计数。
    如果shared_ptr引用计数为0,即使有weak_ptr指向对象,对象还是会释放。

    // weak_ptr操作
    weak_ptr<T> w;  // 空weak_ptr可以指向类型为T的对象
    weak_ptr<T> w(sp); // weak_ptr w与shared_ptr sp指向相同对象,T必须能转化为sp指向的类型
    
    w = p; // p可以是一个shared_ptr或waek_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
    

    创建weak_ptr时,需要用shared_ptr来初始化

    auto p = make_shared<int>(42);
    weak_ptr<int> wp(p); // wp 弱共享p;p的引用计数未改变
    

    由于weak_ptr指向的对象可能不存在,需要调用lock,检查weak_ptr指向的对象是否存在。

    if (shared_ptr<int> np = wp.lock()) {// 如果np不为空则条件成立
      // np 与wp共享,指向同一个对象
    }
    

    核查指针类*
    示例,将StrBlobPtr定义为一个伴随指针类,保存weak_ptr指向成员data。使用weak_ptr不会影响给定StrBlob所指向的vector生命周期,但是可以阻止用户访问不再存在的vector的企图。
    StrBlobPtr有2个数据成员:wptr,要么为空,要么指向一个StrBlob中的vector: curr,保存当前对象所表示的元素的下标。我们的指针类也有一个check成员来检查解引用StrBlobPtr是否安全。

    // 尝试范围不存在的元素,StrBlobPtr抛出一个异常
    class StrBlobPtr {
    public:
      StrBlobPtr(): curr(0) {} // 默认构造函数,将curr显示初始化为0,并将wptr隐式初始化为空weak_ptr
      StrBlobPtr(StrBlob &a, size_t sz = 0) : wptr(a.data), curr(sz) { }
      std::string& deref() const;
      StrBlobPtr& incr(); // 前缀递增
    
    private:
      // 若检查成功,check返回一个指向vector的shared_ptr
      std::shared_ptr<std::vector<std::string>> check(std::size_t, const std::string&) const;
      // 保存weak_ptr,意味着底层vector可能会被销毁
      std::weak_ptr<std::vector<std::string>> wptr;
      std::size_t curr; // 在数组中的当前位置
    };
    
    std::shared_ptr<std::vector<std::string>> 
    StrBlobPtr::check(std::size_t, const std::string&) cosnt {
      auto ret = wptr.lock(); // wptr指向的vector内存不一定存在了,需要检查
      if (!ret) 
        throw std::runtime_error("unbound StrBlobPtr");
      if (i >= ret->size())
        throw std::out_of_range(msg);
    
      return ret; // 指向vector的shared_ptr
    }
    
    // 解引用weak_ptr,这里的例子是先用check获取weak_ptr wptr对应的shared_ptr,再解引用的
    std::string& 
    StrBlobPtr::deref() const {
      auto p = check(curr, "dereference past end");
      return (*p)[curr]; // p是指向vector的shared_ptr,*p代表对象vector,(*p)[curr]代表vector中下标为curr的元素
    }
    
    // 返回递增后的对象的引用
    StrBlobPtr& StrBlobPtr::incr() {
      // 如果curr已经位于容器末尾,则不能递增
      check(curr, "increment past end of StrBlobPtr");
      ++curr; // 递增当前位置
      return *this;
    }
    
    // StrBlob类
    class StrBlobPtr;
    class StrBlob {
      friend class StrBlobPtr; // 为了让StrBlobPtr访问StrBlob的私有成员data
      // 为StrBlob定义begin和end操作,返回一个指向它自身的StrBlobPtr
      StrBlobPtr begin() { return StrBlobPtr (*this); }
      StrBlobPtr end() {
        auto ret = StrBlobPtr(*this, data->size());
        return ret;
      }
    }
    

    12.2 动态数组

    vector, string等对象都在连续内存中保存元素,容器需要重新分配内存时,必须一次性为很多元素分配。
    为此,C++和标准库定义了另一种new表达式语法,可以分配并初始化一个对象数组:名为allocator的类,允许分配和初始化分离。
    使用allocator通常会提供更好的性能和更灵活的内存管理能力。

    使用容器类和分配动态数组的类
    建议:大多数应用应该使用标准库容器而不是动态分配的数组。使用容器更简单,更不容易出现内存关联错误,并且可能有更好的性能。

    使用容器的类,可以使用默认版本的拷贝、赋值和析构操作。
    分配动态数组的类,必须定义自己的操作,中拷贝、复制以及销毁对象时管理所关联的内存。

    12.2.1 new和数组

    new T[]分配得到的“动态数组”并不是一个数组类型,而是一个元素类型指针。

    // new分配对象数组,方括号必须指定要分配对象的数目,可以不是常量,但必须是整型
    int *pia = new int[get_size()]; // pia 指向第一个int
    
    // 用typedef表示数组类型别名
    typedef int arrT[42]; // arrT表示42个int的数组类型
    int *p = new arrT;  // <=> int *p = new int[42];
    

    初始化动态分配对象的数组
    如果初始化器指定的元素个数 > 分配的数组大小,则编译会报错:初始化无法从initialist_list转换成int[10];
    如果初始化器指定的元素个数 <= 分配的数组大小,则开始部分元素用初始化器初始化,后面的元素按值初始化。

    int *pia = new int[10]; // 10个未初始化int
    int *pia2 = new int[10](); // 10个值初始化为0的int
    int *psa = new string[10]; // 10个空串
    int *psa2 = new string[10](); // 10个空串
    
    // 花括号列表初始化
    int *pia3 = new int[10]{0,1,2,3,4,5,6,7,8,9}; // 10个int用列表中的初始化器初始化
    string *psa3 = new string[10]{"a", "an", "the", string(3, 'x')}; // 前4个用给定初始化器初始化,剩余的进行值初始化
    

    动态分配一个空数组是合法的*
    静态空数组非法,动态空数组是合法的。

    char arr[0]; // 错误:不能定义长度为0的数组,数组长度至少为1
    char* cp = new char[0]; // 正确:但不能解引用cp,即使解引用也不会报错,会警告使用未初始化的内存"cp"
    

    释放动态数组
    delete释放动态数组

    delete p; // p必须指向一个动态分配的对象或为空
    delete [] pa; // pa必须指向一个动态分配的数组或为空
    
    // 释放一个类型别名定义的数组类型,也必须按释放数组形式释放
    typedef int arrT[42];
    int* p = new arrT;
    delete [] p;
    

    智能指针和动态数组
    标准库提供了unique_ptr,用于管理new分配的数组,对象类型后有一对空方括号。

    unique_ptr<int[]> up(new int[10]); // 注意unique_ptr对象类型的[]
    up.release(); // 自动用delete[] 销毁其指针
    
    // 指向数组的unique_ptr不支持成员访问运算符(.和 ->)
    // 访问unique_ptr指向的动态数组
    for (size_t i = 0; i != 10; ++i)
      up[i] = i; // 为每个元素赋值
    

    shared_ptr不直接支持管理动态数组。如果希望用shared_ptr管理一个动态数组,必须提供自定义的删除器。

    // 为使用shared_ptr管理动态数组,必须提供删除器
    shared_ptr<int> sp(new int[10], [](int* p){ delete [] p;}); // 绑定动态数组和shared_ptr,注意对象类型。删除器是lambda表达式
    sp.reset(); // 使用提供的lambda释放数组
    
    // 利用shared_ptr访问数组,不支持下标运算符,不支持指针的算术运算
    for (size_t i = 0; i != 10; ++i)
      *(sp.get() + i) = i; // 使用get获得内置指针
    

    12.2.2 alllocator类

    new局限性:new分配内存时,和对象构造组合到了一起。类似的,delete将对象析构和内存释放组合在一起。也就是new对象时,会调用构造函数进行构造,而有时我们并不希望分配内存就立即进行构造。
    allocator能将内存分配和对象构造分离,只有真正需要时才真正执行对象创建。它分配的内存是原始的、未构造的。

    头文件<memory>
    allocator类的使用示例

    // 直接使用new/delete将内存分配和构造绑定到一起,可能导致不必要的浪费
    string* const p = new string[n]; // 构建n个string
    string s;
    string* q = p; // q指向第一个string
    while (cin >> s && q != p + n) {
      *q++ = s;  // 赋予*q新值(拷贝),并移动到下一个位置
    }
    
    const size_t size = q - p;
    // 使用数组p
    
    delete[] p; // 释放动态数组p
    
    // allocator将内存分配和构造分离
    allocator<string> alloc; // 定义allocator,能分配string
    auto p = alloc.allocate(n); // 分配n个未初始化的string。n不能省略。
    auto q = p;
    alloc.construct(p++, "hello"); // 在p指向的内存中构造一个string对象为"hello", *q = "hello"
    alloc.construct(p++, ", this is mike"");  // *q = ", this is mike"
    alloc.construct(p++, 3, '!'); // *q = "!!!"
    
    cout << *q << endl; // 正确:q为初始化的初始区域
    cout << *p << endl; // 错误:此时p已经指向未初始化区域
    
    // 销毁对象
    while (p != q)
      alloc.destroy(--p); // 销毁对应.construct构造的string对象
    
    // 释放内存
    alloc.deallocate(p, n); // 释放对应.allocate分配的内存,这里n必须与申请的内存大小n一样,表示要释放的元素个数
    

    拷贝和填充未初始化内存的算法
    allocator的两个伴随算法,可在未初始化内存中创建对象。

    // allocator算法:这些函数中给定目的位置创建元素,不是由系统分配内存
    uninitialized_copy(b, e, b2);  // 从迭代器范围[b, e)拷贝元素到b2为起始迭代器的原始内存。要求b2指向的内存必须足够大,能存储输入元素的拷贝
    uninitialized_copy_n(b, n, b2); // 从迭代器b开始,拷贝n个元素到b2
    unintialized_fill(b, e, t); // 迭代器范围[b, e)用值t填充
    unintialized_fill_n(b, n, t); // 从b开始n个元素,用值t填充
    
    // 示例:一个vector<int>容器,将其内容拷贝到动态内存,占用动态内存一半空间,另外一半用固定值填充
    vector<int> vec = {...}; // 初始化vector<int>
    allocator<int> alloc; // 定义allcator
    // 分配vector 2倍空间
    auto p = alloc.allocate(vec.size() * 2);
    
    auto q = unitialized_copy(vec.begin(), vec.end(), p); // 用迭代器范围值,填充未初始化内存的前一半
    uninitialized_fill_n(q, vec.size(), 42); // 用固定值42填充剩下一半未初始化内存
    
    // 释放内存
    alloc.deallocate(q, vec.size() *2);
    
  • 相关阅读:
    祖国,让我为您写首歌
    提升信任度,是做网络营销成功的必由之路
    名字作诗已成流行语,你“OUT”了吧?
    腾飞天涯
    沈阳首个文化强市评价体系出炉,为沈阳文化振兴打分
    寄语“官员诗人”车延高:我挺你
    “自己选的路,就算跪着也要走完”引热议
    我在月光下想你
    国庆献礼谁为重
    gcc和g++的区别
  • 原文地址:https://www.cnblogs.com/fortunely/p/14499370.html
Copyright © 2020-2023  润新知