C++ primer第十二章—动态内存

12.1 动态内存与智能指针

  1. 静态内存:保存局部static对象、类static数据成员和定义在任何函数之外的变量
  2. 栈内存:保存定义在函数内的非static对象
  3. 内存池:被称为自由空间或堆,程序用堆来存储动态内存分配的对象,动态对象的生存期由程序控制
  4. new,在动态内存中为对象分配空间并返回指向该对象的指针
  5. delete,接受一个动态对象的指针并销毁该对象,释放与之关联的内存
  6. shared_ptr允许多个指针指向同一对象,unique_ptr则独占所指向的对象,weak_ptr:伴随类,是一种弱引用,指向shared_ptr所管理的对象。三种类型都定义在memory头文件中。

12.1.1 shard_ptr类

shared_ptr<string> p1;  // shared_ptr,可以指向string
shared_ptr<list<int>> p2;   // shared_ptr,可以指向int的list
if (p1 && p1->empty()) {
	*p1 = "Hi";   //如果p1指向一个空string,解引用p1,浆新值赋予string
}
  1. shared_ptr和unique_ptr都支持的操作
    在这里插入图片描述
  2. shared_ptr独有的操作
    在这里插入图片描述

1. make_shared函数

  1. 头文件 #include<memory>
shared_ptr<string> sp2 = make_shared<string>();   //初始化智能指针
shared_ptr<string> sp3 = make_shared<string>("b");//初始化智能指针
auto sp4 = make_shared<vector<string>>();   //sp4指向一个动态分配的空vector<string>
  1. 我们可以认为每个shared_ptr都有一个关联的计数器,通常称其为引用计数,无论我们拷贝一个share_ptr,计数器都会递增。当我们给一个shared_ptr赋值或者shared被销毁,计数器就会递减。当用一个shared_ptr初始化另外一个shared_ptr,或将它作为参数传递给一个函数以及作为函数的返回值(赋值给其他的),计数器都会递增。一旦一个share_ptr的计数器变为0,它就会释放自己所管理的对象。
  2. sp1 = sp2; sp2计数器值增加,右值的计数器增加,左值指向的对象的计数器减少,减少为0时自动释放对象。
auto r = make_shared<int>(53);  // r指向的int只有一个引用者
r = q;    // 递增q指向的对象的引用计数
		// 递减r指向的对象的引用计数
		// r原来指向的对象的计数值为0,自动释放内存
  1. 当指向对象的最后一个shared_ptr 被销毁时,shared_ptr 类会自动销毁此对象。它是通过特殊的成员函数析构函数来控制对象销毁时做什么操作。对于一块内存,shared_ptr 类保证只要有任何shared_ptr 对象引用它,它就不会被释放。
  2. 如果我们忘记了销毁程序不再需要的shared_ptr,程序仍然会正确运行,但会浪费内存。如果你将shared_ptr存放于一个容器中,而后不在需要全部元素,而只使用其中的一部分,要记得调用erase删除不再需要的那些元素。
  3. 程序使用动态内存的三种原因:
  • 程序不知道自己需要使用多少对象
  • 程序不知道所需对象的准确类型
  • 程序需要在多个对象间共享数据。
  1. 我们希望定义一个Blob类,保存一组元素,与容器不同,我们希望Blob对象的不同拷贝之间共享相同的元素。既当我们拷贝一个Blob时,原Blob对象及其拷贝应该引用相同的底层元素。
Blob<string> b1;  // 空Blob
{
	Blob<string> b2 = {"a", "an", "the"};
	b1 = b2;          // b1和b2共享相同的元素 
}    // b2被销毁了,但b2中的元素不能被销毁
     // b1指向最初由b2创建的对象
  1. 定义一个管理string的类,命名为StrBlob。
#include <iostream>
#include <string>
#include <memory>             // 智能指针和动态分配内存
#include <vector>
#include <initializer_list>   // 初始值列表
#include <stdexcept>
using namespace std;
 
class StrBlob
{
    public:
        typedef svector<string>::size_type size_type;   // 使用类型别名
        
        StrBlob();
        StrBlob(initializer_list<string> il);
        size_type size() const { return data->size(); }
        bool empty() { return data->empty(); }
        //添加删除元素
        void push_back(const std::string &s){ data->push_back(s); }
        void pop_back();
        //访问元素
        string& front();
        string& back();
        const string& front()const; 
        const string& back() const;
 
    private:
        shared_ptr<vector<string>> data;      // 使用智能指针指向数组
        //private 检查函数。
        void check(size_type i, const string &msg)const;
};
 
//默认构造函数
StrBlob::StrBlob():
    data(make_shared<vector<string>>()) { }
//拷贝构造函数
StrBlob::StrBlob(initializer_list<string> il):
    data(make_shared<vector<string>>(il)) { }
 
 
void StrBlob::check(size_type i, string &msg)const
{
    if(i >= data->size())
        throw out_of_range(msg);
}
 
const string& StrBlob::back()const
{
    check(0, "back on empty StrBlob");
    return data->back();
}

//避免代码重复和编译时间问题,用non-const版本调用const版本
//在函数中必须先调用const版本,然后去除const特性
//在调用const版本时,必须将this指针转换为const,注意转换的是this指针,所以<>里面是const StrBlob* 是const的类的指针。
//调用const版本时对象是const,所以this指针也是const,通过转换this指针才能调用const版本,否则调用的是non-const版本,non-const调用non-const会引起无限递归。
//return时,const_cast抛出去除const特性。
 
string& StrBlob::back()
{
    const auto &s = static_cast<const StrBlob*>(this)->back(); //auto前面要加const,因为auto推倒不出来const。
    return const_cast<string&>(s);
}
 
const string& StrBlob::front()const
{
    check(0, "front on empty StrBlob");
    return data->front();
}
 
string& StrBlob::front()
{
    const auto &s = static_cast<const StrBlob*>(this)->front();
    return const_cast<string&>(s);
}
 
void StrBlob::pop_back()
{
    check(0, "pop_back on empty StrBlob");
    data->pop_back();
}
 
int main()
{
    shared_ptr<StrBlob> sp;
    StrBlob s({"wang","wei","hao"});
    StrBlob s2(s);//共享s内的数据
    string st = "asd";
    s2.push_back(st);
    //s2.front();
    std::cout << s2.front() << std::endl;
    std::cout << s2.back() << std::endl;
}
// 可以输出s和s2的size( )是相等的证明他们共享的是同一块内存。

12.1.2 直接管理内存

  1. 使用new动态分配内存和初始化对象
#include <iostream>
#include <string>
#include <vector>
 
using namespace std;
 
int main()
{
    //在自由空间分配的内存是无名的,因此new无法为其分配的对象命名,而是返回该指向该对象的一个指针。
    int *p = new int;
    string *ps = new string;   // 初始化为空string
    int *pi = new int;   //pi指向一个未初始化的对象
    int *p2 = new int(10);
    string *p3 = new string(10,'a');
    vector<string> *p4 = new vector<string>{"a","b","c"};
    //也可以对动态分配的对象进行值初始化,只需在类型名之后加一对空括号
    string *p5 = new string(); //值初始化
    string *p6 = new string;   //默认初始化
    int *p7 = new int;         //但是对于内置类型是未定义的。*p7值未定义
    //对动态分配的对象进行初始化通常是个好主意。
 	int *p8 = new int();      //值初始化为0
 	
    //auto 
    auto p8 = new auto("abc");
    auto p9 = new auto{1,2,3}; //error  ??
 
    //const
    //一个动态分配的const对象必须进行初始化。
    const string *p10 = new const string("aha");
 
    //内存耗尽
    int *p11 = new int;  //如果分配失败,new会抛出一个std::bad_alloc
    int *p12 = new (nothrow) int; //如果分配失败返回一个空指针。 bad_alloc和nothrow定义在#include <new>
}
  1. delete p;释放p所指向的对象的那块内存区域,释放后p仍然指向那块区域(测试时输出的地址仍然相同),但是释放后输出的对象已无效。一般我们可以在释放delete p后, p = nullptr。这样明确指针不指向其他的区域。
int i, *pi1 = &i, *pi2 = nullptr;
delete i;        // 错误:i不是一个指针
delete *pi1;     // 错误:pi1不是一个动态分配的指针
delete *pi2;     // 正确:pi2是一个空指针

const int *pci = new const int(1024);
delete pci;   // 正确

int  *p = new int (42);
auto q = p;
delete p;
p = nullptr;   //这里对于q依旧没有效果,只能提供有限的保护
  1. 空悬指针:指向一块曾经保存数据现在已经无效的内存的指针。

12.1.3 shard_ptr和new结合使用

在这里插入图片描述
在这里插入图片描述

  1. 可以用new来初始化智能指针
  2. 接受指针参数的智能指针构造函数是explicit(避免隐式转换)的,我们不能将一个内置指针隐式转换为一个智能指针。
shared_ptr<int> p2(new int(1024));   // 正确:使用了直接初始化形式
shared_ptr<int> p1 = new int(1024);   // 错误:不接受隐式初始化
shared_ptr<int> clone(int p){
	return new int(p);    // 错误:必须进行隐式转换
}
shared_ptr<int> clone(int p){
	return shared_ptr<int>(new int(p));    // 正确
}
  1. 默认情况下,一个用来初始化智能指针的普通指针必须指向一块动态分配的内存,因为智能指针默认使用delete释放它所关联的对象。如果将智能指针绑定到其他类型的指针上,我们必须自己定义自己的释放操作。
  2. 当将一个shared_ptr 绑定到一个普通指针时,我们就将内存管理交给了shared_ptr,之后我们就不应该使用内置指针来访问shared_ptr指向的内存了。使用内置指针来访问智能指针所附则的对象是非常危险的,我们不知道对象何时被销毁。
int *x = new int(1024);    // 危险:x是一个普通指针,不是一个智能指针
process(x);     // 错误:不能将int*转换成智能指针
process(shared_ptr<int>(x));   // 合法,但是内存会被释放
int j = *x;     // 错误:内存被释放
  1. 永远不要用get初始化另一个智能指针或者为另一个智能指针赋值。普通指针不能自动转化为智能指针。使用get返回的指针不能使用delete。
{
    auto p = make_shared<int>(20);
    auto q = p.get();
    delete q;    // delete q释放一次内存,离开作用域再次释放内存,一共释放了两次
}

12.1.4 智能指针和异常

  1. 使用智能指针,即使程序块过早结束,智能指针类也能确保内存不再需要时将其释放。但是普通指针就不会。
void fun( )
{
        int *p = new int(42);
        //如果这时抛出一个异常且未捕获,内存不会被释放,但是智能指针就可以释放。
        delete p;
}
  1. 标准很多都定义了析构函数,负责清理对象使用的资源,但是一些同时满足c和c++的设计的类,通常都要求我们自己来释放资源。通过智能指针可以很好的解决这个问题。
struct destination;    // 表示我们正在连接什么
struct connection;     // 使用连接所需的信息
connection connect(destination*);     // 打开连接
void disconnect(connect);        // 关闭给定的连接
void f(destination &d)
{
        connection c  = connect(&d);
        disconnect(d);//如果没有调用disconnect,那么永远不会断开连接。
}


// 优化

void end_disconnect(connection*p) {disconnect(p);} 
void f(destination &d)
{
        connection c  = connect(&d);
        shared_ptr<connection> p(&c, end_disconnect);
        // 使用连接
        // 当f退出时(即使是由于异常退出),connection会被正确关闭
}
  1. 智能指针的陷阱
  • 不使用相同的内置指针值初始化(或reset)多个智能指针:多个智能指针还是单独的指向内置指针的内存,use_count分别为1
  • 不delete get( )返回的指针
  • 不使用get( )初始化或reset另一个智能指针
  • 如果你使用get( )返回的指针,记住当最后一个对应的智能指针销毁后,你的指针就变得无效了
  • 如果你使用智能指针管理的资源不是new分配的内存,记住传递给它一个删除器(删除函数向上面的disconnect( ))
  1. shared_ptr 的传递删除器(deleter)方式比较简单, 只需要在参数中添加具体的删除器函数名, 即可; 注意是单参数函数;
  2. unique_ptr 的删除器是函数模板(function template), 所以需要在模板类型传递删除器的类型(即函数指针(function pointer)), 再在参数中添加具体删除器;

12.1.5 unique_ptr

在这里插入图片描述

  1. 一个unique_ptr 拥有它所指向的对象,和shared_ptr不同,某个时刻只能有一个unique_ptr 指向一个给定对象,当unique_ptr 被销毁时,对象也被销毁
  2. unique没有类似make_shared,必须手动new,将其绑定。由于unique_ptr独占它所指向的对象,因此他不支持普通的拷贝和赋值。但是有种特殊的拷贝可以支持:我们可以拷贝或赋值一个即将要被销毁的unique_ptr。
  3. 允许返回一个局部的拷贝对象或者从函数返回unique_ptr。
#include <memory>
#include <iostream>
#include <string>
 
using namespace std;
 
unique_ptr<int> clone(int p)
{
    unique_ptr<int>q(new int(p));
    return q;
    //return unique<int>q(new int(p));  // 两种情况都满足条件
}

int main()
{
    unique_ptr<string>p(new string("aaa"));
    shared_ptr<string>p2(new string("aaa"));
    //unique_ptr<string>p3(p);   error:不能拷贝
    //unique_ptr<string>p4 = p;  error:不能赋值
    unique_ptr<string>p5;
    string s = "a";
    //p5.reset(&s);              error:两次释放
 
    //两种转移所有权的方法
    unique_ptr<string>p6(p.release());//p.release(),释放p对指针对象的控制权,返回指针并将p置空,并不会释放内存
    unique_ptr<string>p7;
    p7.reset(p6.release());           //p6释放控制权,p7指向这个对象。
 	
	// 如果不用另一个智能指针来保存release返回的指针,则需要负责资源释放
	p2.release();   // 错误:p2不会释放内存,而且我们丢失了指针
	auto p = p2.release();    // 正确:但必须记得手动释放
	delete p;

    //特殊的拷贝和赋值
    int i = 10;
    clone(i);
}
  1. 类似shared_ptr, unique_ptr 默认情况下用delete释放它指向的对象,和shared_ptr 一样我们可以重载一个unique_ptr 中默认的删除器类型。重载一个unique_ptr 中的删除器会影响到unique_ptr 类型及如何构造该类型的对象,我们必须在尖括号中unique_ptr 指向类型之后提供删除器的类型,在创建或reset一个这种unique_ptr类型的对象时,必须提供一个指定类型的可调用对象(删除器)。
#include <memory>
#include <iostream>
 
using namespace std;
 
typedef int connection;
 
connection* connect(connection *d)
{
    cout << "正在连接..." << endl;
    d = new connection(40);
    return d;
}
 
void disconnect(connection *p)
{
    cout << "断开连接..." << endl;
}
 
int main()
{
    connection *p,*p2;
    p2 = connect(p);
    cout << p << endl; 
    cout << *p2 << endl;
    unique_ptr<connection, decltype(disconnect)*> q(p2,disconnect);
    //在尖括号中提供类型,圆括号内提供尖括号中的类型的对象。
    //使用decltype()关键字返回一个函数类型,所以必须添加一个*号来指出我们使用的是一个指针
}

12.1.6 weak_ptr

在这里插入图片描述

  1. weak_ptr 是一种不控制对象生存期的智能指针,它指向由一个shared_ptr 管理的对象。
auto p = make_shared<int>(42);
weak_ptr<int> wp(p); //弱指针共享p,但p的引用计数没有改变
  1. 由于对象可能不存在,我们不能使用weak_ptr直接访问对象,而必须调用lock检查weak_ptr指向的对象是否存在。
if (shared_ptr<int> np = wp.lock()){  // 如果np不为空则条件成立
	// 在if中np与p共享对象(利用np访问共享对象)
}
#include <memory>
#include <iostream>
 
using namespace std;
 
int main()
{
    shared_ptr<int>p0 = make_shared<int>(42);
    auto p1 = p0;
    cout << "p1 count:" << p1.use_count() << endl;
    weak_ptr<int>p2;
    weak_ptr<int>p3(p1);  //与p1指向相同的对象
    p3 = p0;              //与p0指向相同的对象
    p2 = p3;
    cout << "p2 count:" << p2.use_count() << endl;
    //w.reset() 将w置空
    p2.reset();           //将p2置空
    cout << "p2 count:" << p2.use_count() << endl;
    cout << "p3 count:" << p3.use_count() << endl;
    //w.expired() 若w.use_count()为0,返回true,否则返回false    expired(过期的)
    //w.expried() 也就是判断shared_ptr的count为0吗。
    cout << "p2 expired:" << p2.expired() << endl;
    cout << "p3 expired:" << p3.expired() << endl;
    //w.lock()    如果expired为true,返回一个空的shared_ptr,否则返回一个指向w的对象的shared_ptr
    //w.lock()    也就是如果shared_ptr的count为0,返回空,不为0,返回shared_ptr。
    auto p4 = p2.lock();
    cout << "p4 count:" << p4.use_count() << endl;
    auto p5 = p3.lock();//引用计数加1
    cout << "p5 count:" << p5.use_count() << endl;
}
  1. std::weak_ptr 是一种智能指针,它对被 std::shared_ptr 管理的对象存在非拥有性(“弱”)引用。在访问所引用的对象前必须先转换为 std::shared_ptr。
  2. std::weak_ptr 用来表达临时所有权的概念:当某个对象只有存在时才需要被访问,而且随时可能被他人删除时,可以使用std::weak_ptr 来跟踪该对象。需要获得临时所有权时,则将其转换为 std::shared_ptr,此时如果原来的 std::shared_ptr被销毁,则该对象的生命期将被延长至这个临时的 std::shared_ptr 同样被销毁为止。此外,std::weak_ptr 还可以用来避免 std::shared_ptr 的循环引用。
  3. 定义一个StrBlobPtr(内部weak_ptr)类打印StrBlob中的元素,上面的StrBlobPtr 例子的改进。StrBlobPtr内部是weak_ptr实现的,它实际上就是一个助手类,作用就是类似一个旁观者,一直观测StrBlob的资源使用情况。
  4. 通过使用weak_ptr,不会影响一个给定的StrBlob所指向的vector的生存期,但是可以阻止用户访问一个不再存在的vector的请求。
/* 
 *避免拷贝,多个指针共用一个vector<string>
 *使用weak_ptr访问共享的对象
 * */
 
#include <iostream>
#include <vector>
#include <string>
#include <initializer_list>
#include <memory>
#include <stdexcept>
#include <fstream>
#include <sstream>
using namespace std;
 
class StrBlob;
class StrBlobPtr;
 
 
class StrBlob
{
    public:
        friend class StrBlobPtr;
        typedef vector<string>::size_type size_type;
        StrBlob(); //默认构造函数
        StrBlob(initializer_list<string> il); //拷贝构造函数
        size_type size() const { return data->size(); }      //对data进行解引用就是对vector<string>操作
        string& front();
        string& back();
        const string& front()const;
        const string& back()const;
        void push_back(const string &s) { data->push_back(s); }
        void pop_back();
        //StrBlobPtr begin() { return StrBlobPtr(*this); }
        //StrBlobPtr end() { auto ret = StrBlobPtr(*this, data->size());  
        //                   return ret; }
 
    private:
        void check(size_type sz, std::string msg)const;
        std::shared_ptr<std::vector<std::string>>data;
};
 
string& StrBlob::front()
{
    const auto &s = static_cast<const StrBlob*>(this)->front();
    return const_cast<std::string&>(s);
}
 
string& StrBlob::back()
{
    const auto &s = static_cast<const StrBlob*>(this)->back();
    return const_cast<std::string&>(s);
}
 
const string& StrBlob::front()const
{
    check(0, "front on empty vector");
    return data->front();
}
 
const string& StrBlob::back()const
{
    check(0, "back on empty vector");
    return data->back();
}
 
void StrBlob::check(size_type sz, std::string msg)const
{
    if(sz >= data->size())
        throw std::out_of_range(msg);
}
 
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)) { }
 
/* --------------------------------------------------------------------------------- */
 
//必须定义在StrBlobPtr的后面
//否则error: invalid use of incomplete type ‘class StrBlob’
class StrBlobPtr
{
    public:
        friend StrBlob;    // 声明为友元类
        StrBlobPtr():curr(0){ }
        StrBlobPtr(StrBlob &s, size_t sz = 0):    // 注意这里不能将StrBlobPtr绑定到一个const StrBlob对象
            wptr(s.data), curr(sz){ }   // 用智能指针初始化并设置好下标
        std::string& deref()const;//返回当前string
        StrBlobPtr& incr(); //递增
 
    private:
        shared_ptr<vector<string>> check(size_t i, const string &msg)const;
        weak_ptr<vector<string>> wptr;
        size_t curr;  //当前下标
};
 
StrBlobPtr& StrBlobPtr::incr()
{
    check(curr, "increment past end of StrBlobPtr");
    ++curr; //推进当前位置。  
    return *this;                    //为什么要return *this, 如果再次自加可以重复,举个例子就像赋值一样 a = b = c;  如果不返回对象不能继续赋值。
}                                    //return *this是一份拷贝。 return this是地址。
 
string& StrBlobPtr::deref()const
{
    auto p = check(curr, "dereference past end"); //shared_ptr引用计数会增加,但是作用域结束后,引用计数又会减1
    return (*p)[curr];    //p是所指的vector
}
 
//check检查是否存在shared_ptr和大小
std::shared_ptr<std::vector<std::string>> StrBlobPtr::check(std::size_t i, const std::string &msg)const
{
    auto ret = wptr.lock(); //检查是否存在,存在返回shared_ptr,不存在返回空的shared_ptr.
    if(!ret)
        throw std::runtime_error("unbound StrBlobPtr");
    if(i >= ret->size())
        throw std::out_of_range(msg);
    return ret;
}
 
 
int main(int argc, char*argv[])
{
    std::fstream is(argv[1]);
    std::string s;
    StrBlob S;
    while(std::getline(is, s))
    {
        std::string temp;
        std::istringstream ist(s);
        while(!ist.eof())
        {
            ist >> temp;
            S.push_back(temp);
        }
    }
 
    std::cout << "size:" << S.size() << std::endl;
 
    StrBlobPtr sp(S);
    for(auto i = 0; i < S.size(); ++i)
    {
        std::cout << sp.deref() << std::endl;
        sp.incr();
    }
}

12.2 动态数组

  1. new和delete一次只能分配和释放一个对象,但有时我们需要一次为很多对象分配内存的功能,C++和标准库引入了两种方法,另一种new 和 allocator。
  2. 使用allocator 通常会提供更好的性能和更灵活的内存管理能力。
  3. 大多数应用应该使用标准库容器而不是动态分配的数组,使用容器更为简单、更不容易出现内存管理错误并且可能有更好的性能。
  4. 直到学习完第13章,不要在类内的代码中分配动态内存

12.2.1 new和数组

  1. 动态数组不是数组类型。不能用范围for循环处理动态数组中的元素
int *pia = new int[get_size()];  // 方括号中的量不必是常量
typedef int arrT[42];     // arrT表示42个int的数组类型
int *p = new arrT;         // 分配一个42个int的数组,p指向第一个int
#include <iostream>
#include <memory>
 
using namespace std;
 
typedef int arr[10];
 
int main()
{
    int *p = new int[10];
    int *p2 = new arr;
    for(int i = 0; i < 10; i++)
    {
        p[i] = i;
    }
    for(int i = 0; i < 10; i++)
        cout << p[i] << " ";
    cout << endl;
    //for(const int i : p);         //error:动态分配数组返回的不是数组类型,而是数组元素的指针。所以不能用范围for
    
/*---------------------------------- */
    //初始化动态数组
    int *pi = new int[10];          //未初始化
    int *pi2 = new int[10]();       //初始化为0,且有括号必须为空
    string *ps = new string[10];       //10个空string
    string *ps2 = new string[10]();    //10个空string
    //可以使用列表初始化
    int *pi3 = new int[10]{1,2,3,4,5,6,7,8,9,0};//初始值列表里的值不能多于容量,否则new失败,不会分配内存
    string *ps3 = new string[10]{"a","b","c","d","e","f","g","h","i",string(3,'x')};
    //释放动态数组
    delete []pi3; //必须要加[]括号,且释放动态数字时是逆序释放。如果delete动态数组不加[],行为是未定义的。
 
/*----------------------------------- */
    //智能指针和动态数组,标准库定义了特别的unique_ptr来管理,当uo销毁它管理的指针时,会自动调用delete [];
    int *p5 = new int[10];
    //unique_ptr<int[]> up;   
    unique_ptr<int[]> up(p5);  
    for(int i = 0; i < 10; ++i)
        cout << up[i] << " ";
    cout << endl;
    //如果使用shared_ptr的话我们必须自己定义delete函数
    shared_ptr<int>sp(new int[10], [](int *p) { delete []p;});
    //智能指针不支持算数类型,如果要访问数组中的元素,必须使用get函数返回一个内置指针。
    cout << (*sp.get()) << endl;
 
}
  1. 如果在delete一个数组指针时忘记加方括号,或者在delete一个单一对象时使用了方括号,编译器可能不会给出警告。
  2. 可以使用unique_ptr管理new分配的数组
unique_ptr<int[]> up(new int[10]);
unique.release();   // 自动用delete[]销毁其指针(跟int型指针不一样?)
for(size_t i = 0; i != 10; ++i){
	up[i] = i;   // 允许使用下标访问
}

在这里插入图片描述
4. 使用shared_ptr不能直接管理动态数组,必须自定义删除器

shared_ptr<int> sp(new int[10], [](int *p){delete []p;});
sp.reset();   // 使用lambda释放数组
// 注意没有下标运算

12.2.2 allocator类

  1. 引入allocator的原因是new类上的缺陷,new它将内存分配和对象构造结合到了一起,可能会造成两次赋值,更重要的是,那些没有默认构造函数的类就不能使用动态分配数组了。
    在这里插入图片描述
  2. allocator类定义在头文件memory中,将内存分配和对象初始化分开。
#include <iostream>
#include <memory>
#include <string>
 
using namespace std;
 
int main()
{
    //allocator<T> a;  定义了一个名为a的allocator对象,可以为T对象分配空间
    allocator<string> alloc;
    //a.allocate(n);  为T分配n个空间  
    string *const p  = alloc.allocate(10);  //为10个string分配了内存,且内存是原始的,未构造的
    cout << sizeof(p) << endl;
    //释放p中地址的内存,这快内存保存了n个T对象,p必须是allocte返回的指针,n必须是allocate(n)的n
    //且在调用deallocate时必须先毁坏这块内存中创建的对象
    alloc.deallocate(p,10);
    //p是allocate返回的指针,construction是传递给类型T的构造函数,在p指向的内存中构造一个对象
    //alloc.construct(p, construction)
    //对p指向的对象进行析构函数。
    //alloc.destroy(p);
    
    allocator<string>alloc2;
    auto const p2 = alloc2.allocate(10);
    auto q = p2;
    auto q2 = p2;
    //为了使用我们allocate的内存,必须用construct构造对象,使用未定义的内存,其行为是未定义的。
    alloc2.construct(q++, "sssss");
    cout << *q2++ << endl;
    alloc2.construct(q++, "he");
    cout << *q2++ << endl;
    alloc2.construct(q++, 10, 'x');
    cout << *q2 << endl;
    //对使用过的内存进行释放,调用string的析构函数,注意不能destory未使用的内存。
    while(q2 != p2)
        alloc2.destroy(q2--);
    //元素被销毁后,我们可以重新使用这块内存,也可以归还给系统
    alloc2.deallocate(p2, 10);
    //deallocate的指针不能为空,必须指向allocate分配的内存,且deallocate和allocate的大小相同。
}
  1. 标准库还为allocator定义了两个伴随算法,在未初始化的内存中创建对象,都定义在头文件memory。
uninitialized_copy(b,e,b2)      b,2是输入容器的迭代器,b2是内存的起始地址,要保证空间足够
uninitialized_copy_n(b,n,b2)    b是输入容器的起始迭代器,复制n个,复制到以b2为起始地址的动态内存中
uninitialized_fill(b,e,t)       b,e是动态内存的起始和终止位置,t是要fill的元素
uninitialized_fill_n(b,n,t)     b是动态内存的起始,fill n个,t是要fill的元素

12.3 使用标准库:文本查询程序

12.3.1 文本查询程序设计

12.3.2 文本查询程序类的定义

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值