C++primer-learning-notes-chapter12

227 阅读21分钟

动态内存

  • 全局对象在程序启动时分配,结束时销毁。局部对象在进入程序块时创建,离开块时销毁。局部static对象在第一次使用前分配,在程序结束时销毁。动态分配对象:只能显式地被释放。
  • 静态内存用来保存局部static对象、类static对象、定义在任何函数之外的变量。栈内存用来保存定义在函数内的非static对象。堆内存,又称自由空间,用来存储动态分配的对象。
  • 动态内存管理: new:在动态内存中为对象分配空间并返回一个指向该对象的指针。 delete:接受一个动态对象的指针,销毁该对象,并释放与之关联的内存。

动态内存和智能指针

  • shared_ptr允许多个指针指向同一个对象。unique_ptr则独占所指向的对象。weak_ptr是一个伴随类,是一种弱引用,指向shared_ptr所管理的对象。头文件memory。

shared_ptr类

shared_ptr<string> p1;
shared_ptr<list<int>> p2;//保存了空指针

if(p1 && p1->empty())
    *p1 = "hi";
  • shared_ptr和unique_ptr都支持的操作
shared_ptr<T> sp 
unique_ptr<T> up	//空智能指针,可以指向类型是T的对象
p	//将p用作一个条件判断,若p指向一个对象,则为true
*p	//解引用p,获得它指向的对象。
p->mem	//等价于(*p).mem
p.get()	//返回p中保存的指针,要小心使用,若智能指针释放了对象,返回的指针所指向的对象也就消失了。
swap(p, q)  p.swap(q)	//交换p和q中的指针
  • shared_ptr独有的操作
make_shared<T>(args)	//返回一个shared_ptr,指向一个动态分配的类型为T的对象。使用args初始化此对象。
shared_ptr<T>p(q)	//p是shared_ptr q的拷贝;此操作会递增q中的计数器。q中的指针必须能转换为T*
p = q	//p和q都是shared_ptr,所保存的指针必须能互相转换。此操作会递减p的引用计数,递增q的引用计数;若p的引用计数变为0,则将其管理的原内存释放。
p.unique()	//若p.use_count()是1,返回true;否则返回false
p.use_count()	//返回与p共享对象的智能指针数量;可能很慢,主要用于调试。
  • make_shared函数(最安全的分配和使用动态内存的方法)
shared_ptr<int> p3 = make_shared<int>(42); //指向一个值为42的int的shared_ptr
shared_ptr<string> p4 = make_shared<string>(10,'9');//p4指向值为"999999999"的string
shared_ptr<int> p5 = make_shared<int>();
//类似顺序容器的emplace成员

//用auto定义一个对象保存make_shared
auto p6 = make_shared<vector<string>>();//p6指向一个动态分配的空vector<string>
  • shared_ptr的拷贝和赋值
auto p = make_shared<int>(42);//p指向的对象只有p一个引用者
auto q(p);//p和q指向相同的对象,此对象有两个引用者
  • 每个shared_ptr都有一个关联的计数器,通常称其为引用计数。拷贝一个shared_ptr,计数器都会递增,如用一个shared_ptr初始化另一个,或将它作为参数传递给一个函数,以及作为函数的返回值,它所关联的计数器就会递增。当给shared_ptr赋予一个新值或是shared_ptr被销毁,如一个局部的shared_ptr离开其作用域时,计数器会递减。一旦一个shared_ptr的计数器变为0,它就会自动释放自己所管理的对象。
auto r = make_shared<int>(42); //r指向的int只有一个引用者
r = q;//给r赋值,令它指向另一个地址,递增q指向的对象的引用计数,递减r原来指向的对象的引用计数
//r原来指向的对象已没有引用者,会自动释放。
  • shared_ptr自动销毁所管理的对象:当指向一个对象的最后一个shared_ptr被销毁时,shared_ptr类会自动销毁此对象。通过析构函数实现。shared_ptr的析构函数会递减它随指向的对象的引用计数。如果引用计数变为0,shared_ptr的析构函数就会销毁对象,并释放它占用的内存。
  • shared_ptr还会自动释放相关联的内存
shared_ptr<Foo> factory(T arg){   //返回一个shared_ptr,指向一个动态分配的对象
    return make_shared<Foo>(arg);
}

void use_factory(T arg){
    shared_ptr<Foo> p = factory(arg);
}//p离开了作用域,它指向的内存会被自动释放掉
//p是局部变量,在use_factory结束时它将被销毁。p被销毁时,将递减其引用计数并检查它是否为0,此例p是唯一引用factory返回的内存的对象,由于p将要销毁,p指向的这个对象也会被销毁,所占用的内存会被释放。

//如果有其他shared_ptr也指向这块内存,它就不会被释放:
shared_ptr<Foo> use_factory(T arg){
    shared_ptr<Foo> p = factory(arg); //使用p
    return p; //当我们返回p时,引用计数进行了递增操作
} //p离开了作用域,但它指向的内存不会被释放掉

//函数调用者返回一个p的拷贝。当p被销毁时,它所指向的内存还有其他使用者。
  • shared_ptr在无用之后仍然保留的一种可能情况是,你将shared_ptr存放在一个容器中,随后重排了容器,从而不再需要某些元素。这种情况下,你应该确保用erase删除那些不再需要的shared_ptr元素。
  • 使用了动态生存期的资源的类
    • 程序不知道自己需要使用多少对象
    • 程序不知道所需对象的准确类型
    • 程序需要在多个对象间共享数据
  • 动态内存是为了让多个对象能共享相同的底层数据。目前为止,我们使用过的类中,分配的资源都与对应对象生存期一致。例如,每个vector拥有其自己的元素。当我们拷贝一个vector时,原vector和副本vector中的元素是相互分离的。
vector<string> v1;
{
    vector<string> v2 = {};
    v1 = v2;
}//v2被销毁,其中元素也被销毁
//v1有三个元素,是原来v2中元素的拷贝
  • 但某些类分配的资源具有与原对象想独立的生存期。
//希望定义一个类
Blob<string> b1;
{
    Blob<string> b2 = {};
    b1 = b2; //b1,b2共享相同的元素
}//b2被销毁了,但b2中的元素不能销毁
//b1指向最初由b2创建的元素
//此例中,b1和b2共享相同的元素,当b2离开作用域时,这些元素必须保留,因b1仍然在使用它们。

定义StrBlob类

  • 为了保证vector中的元素继续存在,我们将vector保存在动态内存中。
  • 构造函数的initializer类型参数,可以接受一个初始化器的花括号列表。
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){data->push_back(t);}
    void pop_back();
    
    std::string& front();
    std::string& back();
private:
    std::shared_ptr<std::vector<std::string>> data;
    void check(size_type i,const std::string& msg) const;
};

//StrBlob构造函数
StrBlob::StrBlob():data(make_shared<vector<string>>()){}
StrBlob::StrBlob(initializer_list<string> il):data(make_shared<vector<string>>(il)){}
  • pop_back,front,back操作访问vector中的元素。这些操作在试图访问元素之前必须检查元素是否存在。定义了一个名为check的private工具函数,它检查一个给定索引是否在合法范围内。
void StrBlob::check(size_type i,const string& msg) const{
    if(i >= data->size())
        throw out_of_range(msg);
}

string& StrBlob::front(){
    check(0,"front on empty StrBlob");
    return data->front();
}

string& StrBlob::back(){
    check(0,"back on empty StrBlob");
    return data->back();
}

void StrBlob::pop_back(){
    check(0,"pop_back on empty StrBlob");
    data->pop_back();
}
  • StrBlob的拷贝,赋值和销毁:当我们拷贝赋值或销毁一个StrBlob对象时,它的shared_ptr成员会被拷贝,赋值或销毁。拷贝一个shared_ptr会递增其引用计数,将一个shared_ptr赋予另一个shared_ptr会递增赋值号右侧shared_ptr的引用计数,递减左侧shared_ptr的引用计数。引用计数为0,所指向的对象会被自动销毁。对于由StrBlob构造函数分配的vector,当最后一个指向它的StrBlob对象被销毁时,它会随之被自动销毁。

直接管理内存

  • new分配内存,delete释放new分配的内存。容易出错。自己直接管理内存的类不能依赖类对象拷贝,赋值和销毁操作的任何默认定义。智能指针的程序更容易编写和调试。

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

  • 在自由空间分配的内存是无名的,因此new无法为其分配的对象命名,而是返回一个指向该对象的指针。
int* pi = new int; //pi指向一个动态分配的,未初始化的无名对象
string* ps = new string;//初始化为空string
int* pi = new int;//pi指向一个未初始化的int
int *pi = new int(1024);
string *ps = new string(10,'9');
vector<int> *pv = new vector<int>{0,1,2,3,4,5,6,7,8,9};

string *ps1 = new string;//空string
string *ps = new string();//空string
int *pi1 = new int; //默认初始化,*pil值未定义
int *pi2 = new int();//值初始化为0

auto p1 = new auto(obj);
auto p2 = new auto{a,b,c}; //错误:括号中只能有单个初始化器

动态分配的const对象

//用new分配const对象是合法的
const int *pci = new const int(1024);
const string *pcs = new const string;

内存耗尽

int *p1 = new int;//分配失败,new抛出std::bad_alloc
int *p2 = new (nothrow) int;//如果分配失败,new返回一个空指针

释放动态内存

delete p;//p必须是指向一个动态分配的对象或是一个空指针

Foo* factory(T arg){
    return new Foo(arg);
}
void use_factory(T arg){
    Foo *p = factory(arg);
}//p离开了它的作用域,但它所指向的内存并没有被释放
//p是指向factory分配的内存的唯一指针,一旦use_factory返回,程序就没办法释放这块内存了。
void use_factory(T arg){
    Foo *p = factory(arg);
    delete p;
}

Foo* use_factory(T arg){
    Foo *p = factory(arg);
    return p; //调用者必须释放内存
}
  • 常见问题:忘记delete内存,使用已经释放掉的对象,同一块内存释放两次。

delete之后重置指针值

  • 空悬指针:delete之后指针变得无效,但在很多机器上指针仍然保存着动态内存的地址。delete之后,就成了指向一块曾经保存数据对象但现在已经无效的内存的指针。
  • 避免空悬指针的方法:在指针离开其作用域前释放它所关联的内存。或者在delete之后将nullptr赋予指针。
  • 动态内存基本问题是可能有多个指针指向相同的内存,在delete内存之后重置指针的方法只对这个指针有效,对其他仍指向已释放的内存的指针是没有作用的。
int *p(new int(42));
auto q = p;
delete p;  //p和q均变为无效
p = nullptr;//重置p对q没有任何作用
//实际中,查找指向相同内存的所有指针是异常困难的。

shared_ptr和new结合使用

shared_ptr<double> p1;
shared_ptr<int> p2(new int(42));
//接受指针参数的智能指针构造函数是explicit,因此不能将一个内置指针隐式转换为一个智能指针必须使用直接初始化形式。
shared_ptr<int> p1 = new int(42);//wrong
shared_ptr<int> p2(new int(42));
//返回shared_ptr的函数也不能在其返回语句中隐式转换一个普通指针
shared_ptr<int> clone(int p){
    return new int(p);//wrong
    return shared_ptr<int>(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的拷贝,唯一的区别是p将可调用对象d来代替delete。
p.reset()	//若p是唯一指向其对象的shared_ptr,reset会释放此对象。若传递了可选的参数内置指针q,会令p指向q,否则会将p置空。若还传递了参数d,则会调用d而不是delete来释放q。
p.reset(q)	//同上
p.reset(q, d)	//同上

//不要混合使用普通指针和智能指针
void process(shared_ptr<int> ptr){
    //使用ptr
}//ptr离开作用域,被销毁
//值传递,会拷贝,process运行过程中,引用计数值至少为2,process结束,ptr引用计数会递减但不会变为0,因此当局部ptr被销毁时,ptr指向的内存不会被释放

shared_ptr<int> p(new int(42));
process(p);
int i = *p;

//虽不能传递给process一个内置指针,但可以传递一个临时的shared_ptr。
int *x(new int(42));
process(x); //错误,不能转换int*为shared_ptr<int>
process(shared_ptr<int>(x));
int j = *x;//未定义的:x是一个空悬指针。
//临时对象当这个调用所在的表达式结束时就被销毁了。引用计数变为0,内存被释放。但x继续指向已经释放的内存,从而变为一个空悬指针。

//不要用get初始化另一个智能指针或为智能指针赋值。
//智能指针类型定义了一个名为get的函数,它返回一个内置指针,指向智能指针管理的对象。使用get返回的指针的代码不能delete此指针。
shared_ptr<int> p(new int(42));
int *q = p.get();
{
    shared_ptr<int> q;
}
int foo = *p;//未定义的,p为空悬指针
//p和q指向相同的内存,它们是相互独立创建的,因此各自的引用计数都为1。q所在程序块结束时,q被销毁,导致q指向的内存被释放。p被销毁时,这块内存会被第二次delete.

//其他shared_ptr操作
p = new int(1024);//wrong
p.reset(new int(1024));//right,p指向一个新对象
//与赋值类似,reset会更新引用计数,如需要会释放p指向的对象。
if(!p.unique()){
    p.reset(new string(*p));//我们不是唯一用户,分配新的拷贝
}
*p += newVal;//现直到自己是唯一用户,可以改变对象的值。

智能指针与异常

  • 使用智能指针即使程序块过早结束,也能确保在内存不在需要时将其释放。
void f(){
    shared_ptr<int> sp(new int(42));
}//无论是正常退出还是发生了异常,局部对象都会被销毁。

void f(){
    int *ip = new int(42);
    delete ip;
}//new之后在对应的delete之前发生了异常,内存不会被释放
//且当异常未在f中被捕获,则内存就永远不会被释放了。

//智能指针和哑类
struct destination;
struct connection;
connection connect(destination*);
void disconnect(connection);
void f(destination &d){
    connection c = connect(&d);
    //如我们在f退出前忘记调用disconnect,就无法关闭c了
}
//如果connection有一个析构函数,就可以在f结束时由析构函数自动关闭连接。但connection没有析构函数。

//使用我们自己的释放操作
void end_connection(connection *p){disconnect(*p);}
void f(destination& d){
    connection c = connect(&d);
    shared_ptr<connection> p(&c,end_connection);
}//f退出时,即便是由于异常退出,connection会被正确关闭
//p被销毁时,不会对自己保存的指针执行delete,而是调用end_connection。

智能指针的陷阱和规范

  • 不使用相同的内置指针初始化或reset多个智能指针。
  • 不delete get()返回的指针
  • 不使用get()初始化或reset另一个智能指针。
  • 如果你使用get()返回的指针,记住当最后一个对应的智能指针销毁后,你的指针就变的无效了。
  • 如果你使用智能指针管理的资源不是new分配的内存,记住传递给它一个删除器。

unique_ptr

  • 一个unique_ptr拥有它所指向的对象,某一个时刻只能有一个unique_ptr指向一个给定的对象。需要绑定到一个new返回的指针上,初始化unique_ptr必须采用直接初始化形式。
unique_ptr<double> p1;
unique_ptr<int> p2(new int(42));
  • 不支持普通的拷贝或赋值操作
unique_ptr<string> p1(new string("Stegosaurus"));
unique_ptr<string> p2(p1);//wrong
unique_ptr<string> p3;
p3 = p2;//wrong
  • unique_ptr操作
unique_ptr<T> u1	//空unique_ptr,可以指向类型是T的对象。u1会使用delete来是释放它的指针。
unique_ptr<T, D> u2	//u2会使用一个类型为D的可调用对象来释放它的指针。
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)	//令u指向q指向的对象
u.reset(nullptr)	//将u置空
unique_ptr<string> p2(p1.release());//将所有权从p1转移给p2,release将p1置为空。
unique_ptr<string> p3(new string("Trex"));
p2.reset(p3.release());//所有权从p3转移给p2,reset释放了p2原来指向的内存。

p2.release();//wrong,p2不会释放内存,而且我们会丢失了指针
auto p = p2.release();//right,但必须记得delete(p)

//传递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
//向后兼容auto_ptr

//向unique_ptr传递删除器,默认用delete释放它指向的对象。
unique_ptr<objT,delT> p(new objT,fcn);
void f(destination &d){
    connection c = connect(&d);
    unique_ptr<connection,decltype(end_connection)*> p(&c,end_connection);
}

weak_ptr

  • weak_ptr是一种不控制所指向对象生存期的智能指针。
  • 指向一个由shared_ptr管理的对象,不改变shared_ptr的引用计数。
  • 一旦最后一个指向对象的shared_ptr被销毁,对象就会被释放,不管有没有weak_ptr指向该对象。

weak_ptr操作

weak_ptr<T> w	//空weak_ptr可以指向类型为T的对象
weak_ptr<T> w(sp)	//与shared_ptr指向相同对象的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。
auto p = make_shared<int>(42);
weak_ptr<int> wp(p);//wp弱共享p;p的引用计数未改变
//wp指向的对象可能被释放掉,不能使用weak_ptr直接访问对象,必须使用lock
if(shared_ptr<int> np = wp.lock()){
    //if中,np与p共享对象
}

核查指针类

class StrBlobPtr{
public:
    StrBlobPtr():curr(0){}
    StrBlobPtr(StrBlob &a,size_t sz = 0):wptr(a.data),curr(sz){}
    std::string& deref() const;
    StrBlobPtr& incr();
private:
    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 &msg) const{
    auto ret = wptr.lock();
    if(!ret)
        throw std::runtime_error("unbound StrBlobPtr");
    if(i >= ret->size())
        throw std::out_of_range(msg);
    return ret;
}//weak_ptr不参与对应的shared_ptr引用计数,StrBlobPtr指向的vector可能已经被释放了,如果
//vector被销毁,lock将返回一个空指针

//指针操作
std::string& StrBlobPtr::deref() const{
    auto p = check(curr,"dereference past end");
    return (*p)[curr];
}

StrBlobPtr& StrBlobPtr::incr(){
    check(curr,"increment past end of StrBlobPtr");
    ++curr;
    return *this;
}

classs StrBlobPtr;
class StrBlob{
    friend class StrBlobPtr;
    StrBlobPtr begin() {return StrBlobPtr(*this);}
    StrBlobPtr end(){
        auto ret = StrBlobPtr(*this,data->size());
        return ret;
    }
};

动态数组

int *pia = new int[get_size()];  //方括号中大小必须是整型,不必是常量
typedef int arrT[42];
int *p = new arrT;//p指向第一个int,int *p = new int[42];
//分配的内存并不是一个数组类型,不能调用begin,end,范围for循环等

//初始化
int *pia = new int[10];  //10个未初始化
int *pia2 = new int[10](); //0
string *psa = new string[10];//空string
string *psa2 = new string[10]();//空string

int *pia3 = new int[10]{0,1,2,3,4,5,6,7,8,9};
string *psa3 = new string[10]{"a","an","the",string(3,'x')};

//动态分配空数组合法
size_t n = get_size();
int *p = new int[n];
for(int *q=p;q!=p+n;++q){}

char arr[0]; //错误,不能定义长度为0的数组
char *cp = new char[0];正确,但cp不能解引用

//释放动态数组
delete p;
delete [] pa;//逆序销毁

typedef int arrT[42];
int *p = new arrT;
delete [] p;

//智能指针和动态数组
unique_ptr<int[]> up(new int[10]);
up.release();//自动用delete[]销毁其指针

for(size_t i = 0;i!=10;++i){
    up[i] = i;
}
//指向数组的unique_ptr
unique_ptr<T[]> u	//u可以指向一个动态分配的数组,整数元素类型为T
unique_ptr<T[]> u(p)	//u指向内置指针p所指向的动态分配的数组。p必须能转换为类型T*。
u[i]	//返回u拥有的数组中位置i处的对象。u必须指向一个数组。

//shared_ptr不直接支持管理动态数组,如需要,必须提供自己定义的删除器。
shared_ptr<int> sp(new int[10],[](int *p){delete [] p;});
sp.reset();

for(size_t i = 0;i!=10;++i){
    *(sp.get() + i) =i;//使用get获取一个内置指针
}

allocator类(分配和初始化分离)

  • 标准库allocator类定义在头文件memory中,帮助我们将内存分配和对象构造分离开。
  • 分配的是原始的、未构造的内存。
string *const p = new string[n];
string s;
string *q = p;
while(cin>>s && q!=p+n)
    *q++ = s;
const size_t size = q - p;
delete[] p;
allocator<string> alloc;//可以分配string的allocator对象
auto const p = alloc.allocate(n);//分配n个未初始化的string
//为n个string分配了内存
  • allocator类及其算法
allocator<T> a	//定义了一个名为a的allocator对象,它可以为类型为T的对象分配内存
a.allocate(n)	//分配一段原始的、未构造的内存,保存n个类型为T的对象。
a.deallocate(p, n)	//释放从T*指针p中地址开始的内存,这块内存保存了n个类型为T的对象;p必须是一个先前由allocate返回的指针。且n必须是p创建时所要求的大小。在调用deallocate之前,用户必须对每个在这块内存中创建的对象调用destroy。
a.construct(p, args)	//p必须是一个类型是T*的指针,指向一块原始内存;args被传递给类型为T的构造函数,用来在p指向的内存中构造一个对象。
a.destroy(p)	//p为T*类型的指针,此算法对p指向的对象执行析构函数。
auto q = p;//q指向最后构造的元素之后的位置
alloc.construct(q++); //*q为空字符串
alloc.construct(q++,10,'c');//*q为ccccccc
alloc.construct(q++,"hi");//*q为hi

cout<<*p<<endl;//正确
cout<<*q<<endl;//错误,q指向未构造的内存

while(q!=p)
    alloc.destroy(--q);
    
alloc.deallocate(p,n);//归还给系统

拷贝和填充未初始化内存的算法,头文件memory中

uninitialized_copy(b, e, b2)	//从迭代器b和e给定的输入范围中拷贝元素到迭代器b2指定的未构造的原始内存中。b2指向的内存必须足够大,能够容纳输入序列中元素的拷贝。
uninitialized_copy_n(b, n, b2)	//从迭代器b指向的元素开始,拷贝n个元素到b2开始的内存中。
uninitialized_fill(b, e, t)	//在迭代器b和e执行的原始内存范围中创建对象,对象的值均为t的拷贝。
uninitialized_fill_n(b, n, t)	//从迭代器b指向的内存地址开始创建n个对象。b必须指向足够大的未构造的原始内存,能够容纳给定数量的对象。
auto p = alloc.allocate(vi.size()*2);//分配比vi中元素所占空间大一倍的动态内存
auto q = uninitialized_copy(vi.begin(),vi.end(),p);//拷贝vi中的元素来构造从p开始的元素
uninitialized_fill_n(q,vi.size(),42);//剩余元素初始化为42

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

void runQueries(ifstream &infile){
    TextQuery tq(infile);
    while(true){
        cout<<"enter word to look for,or q to quit:";
        string s;
        if(!(cin>>s) || s == 'q') break;
        print(cout,tq.query(s)) <<endl;
    }
}

class QueryResult;
class TextQuery{
public:
    using line_no = std::vector<std::string>::size_type;
    TextQuery(std::ifstream&);
    QueryResult query(const std::string&) const;
private:
    std::shared_ptr<std::vector<std::string>> file;
    std::map<std::string,std::shared_ptr<std::set<line_no>>> wm;
};

TextQuery::TextQuery(ifstream &is):file(new vector<string>){
    string text;
    while(getline(is,text)){
        file->push_back(text);
        int n = file->size() - 1;
        istringstream line(text);
        string word;
        while(line>>word){
            auto &lines = wm[word];
            if(!lines)
                lines.reset(new set<line_no>);//分配一个新的set
            lines->insert(n);
        }
    }
}

class QueryResult{
friend std::ostream& print(std::ostream&,const QueryResult&);
public:
    QueryResult(std::string s,std::shared_ptr<std::set<line_no>> p,std::shared_ptr<std::vector<std::string>> f):sought(s),lines(p),file(f){}
private:
    std::string sought;
    std::shared_ptr<std::set<line_no>> lines;
    std::shared_ptr<std::vector<std::string>> file;
}

QueryResult TextQuery::query(const string &sought) const{
    static shared_ptr<set<line_no>> nodata(new set<line_no>);
    auto loc = wm.find(sought);
    if(loc == wm.end())
        return QueryResult(sought,nodata,file);
    else
        return QueryResult(sought,loc->second,file);
}

ostream& print(ostream &os,const QueryResult &qr){
    os<<qr.sought<<"occurs"<<qr.lines->size()<<" "<<make_plural(qr.lines->size(),"times","s")<<endl;
    for(auto num : *qr.lines)
        os<<"\t(line"<<num+1<<")"<<*(qr.file->begin()+num)<<endl;
    return os;
}