《C++ Primer Plus 第6版》第16章 string类和标准模板库 学习笔记——智能指针模板类

本文介绍了C++中的智能指针模板类,包括auto_ptr、unique_ptr和shared_ptr,详细讲解了它们的使用方法、注意事项及优缺点。通过示例代码展示了智能指针如何管理动态内存,特别强调了unique_ptr的移动语义和对内存分配的严格匹配要求。同时,讨论了在不同场景下如何选择合适的智能指针。

主要内容:

  • 标准C++ string类
  • 模板auto_ptr、unique_ptr和shared_ptr
  • 标准模板库(STL)
  • 容器类
  • 迭代器
  • 函数对象(functor)
  • STL算法
  • 模板initializer_list(C++11)

2. 智能指针模板类

智能指针是行为类似于指针的类对象。本节介绍三个可帮助管理动态内存分配的智能指针模板。

先来看需要哪些功能已经这些功能是如何实现的:

void remodel(std::string& str)
{
    std::string* ps = new std::string(str);
    //...
    str = *ps;
    return;
}

如果ps不是一个普通指针,而是一个对象,则可以在对象过期时,让它的析构函数删除指向的内存。这正是auto_ptr、unique_ptr、shared_ptr背后的思想。模板auto_ptr是C++98提供的解决方案,C++11已经将其摈弃,并提供了另外两种解决方案。然而,auto_ptr已经使用了多年,如果编译器不支持其他两种解决方案,auto+ptr将是唯一的选择。

2.1 使用智能指针

三个智能指针模板(auto_ptr、unique_ptr、shared_ptr)都定义了类似指针的对象,可以将new获得(直接或间接)的地址赋给这种对象。当智能指针过期时,其析构函数将使用delete来释放内存。

要创建智能指针对象,必须包含头文件memory,该文件包含模板定义。然后使用通常的模板语法来实例化所需类型的指针。

例如,模板auto_ptr包含如下构造函数:

template<class X> 
class auto_ptr
{
public:
    explicit auto_ptr(X* p = 0) throw();   //throw()意味着构造函数不会引发异常;与auto_ptr一样,throw()也被摈弃
//...
};

请求X类型的auto_ptr将获得一个指向X类型的auto_ptr:

auto_ptr<double> pd(new double);
auto_ptr<string> ps(new string);
unique_ptr<double> pdu(new double);
shared_ptr<string> pss(new string);

因此,要转换remodel()函数,应按下面3个步骤进行:

1. 包含头文件memory;

2. 将指向string的指针替换为指向string的智能指针对象;

3. 删除delete语句

#include <memory>
void remodel(std::string& str)
{
    std::auto_ptr<std::string> ps(new std::string(str))
    //...
    if(weird_thing())
        throw exception();
    str = *ps;
    return;
}

演示如何使用全部三种智能指针:

//程序清单16.5  smrtptrs.cpp
//smrtptrs.cpp -- using three kinds of smart pointers
//requires support of C++11 shared_ptr and unique_ptr
#include <iostream>
#include <string>
#include <memory>

class Report    //用于报告对象的创建和销毁
{
public:
    Report(const std::string s): str(s) { std::cout << "Object created!\n"; }
    ~Report() { std::cout << "Object deleted!\n"; }
    void comment() const {std::cout << str << "\n"; }

private:
    std::string str;
}

int main()
{
    {
        std::auto_ptr<Report> ps (new Report("Using auto_ptr"));
        ps->comment();
    }
    {
        std::shared_ptr<Report> ps (new Report("Using shared_ptr"));
        ps->comment();
    }
    {
        std::unique_ptr<Report> ps (new Report("Using unique_ptr"));
        ps->comment();
    }
    return 0;
}

所有智能指针类都有一个explicit构造函数,该构造函数将指针作为参数,因此不需要自动将指针转换为智能指针对象:

shared_ptr<double> pd;
double* p_reg = new double;
pd = p_reg;         //不允许,这是隐式转换
pd = shared_ptr<double>(p_reg);    //允许,这是显式转换
shared_ptr<double> pshared = p_reg;   //不允许,这是隐式转换
shared_ptr<double> pshared(p_reg);    //允许,这是显式转换

由于智能指针模板类的定义方式,智能指针对象的很多方面都类似于常规指针。先说明对全部三种智能指针都应避免的一点:

string vacation("I wandered lonely as a cloud.");
shared_ptr<string> pvac(&vacation);   //注意不要这样做!!!!!

pvac过期时,将把delete运算符用于非堆内存,这是错误的。

2.2 有关智能指针的注意事项

为何摒弃auto_ptr呢?

观察如下赋值语句:

auto_ptr<string> ps (new string("I reigned lonely as a cloud."));
auto_ptr<string> vocation;
vocation = ps;

如果ps和vocation不采取机制,两个指针指向同一个string对象,这不可接受,因为程序将试图删除同一个对象两次。要避免这种问题,方法有多种:

  • 定义赋值运算符,使之执行深复制。这样两个指针将指向不同的对象,其中的一个对象是另一个对象的副本。
  • 建立所有权(ownership)概念,对于特定的对象,只能有一个只能指针可以拥有它,这样只有拥有对象的只能指针的构造函数会删除该对象。然后,让赋值运算符转让所有权。这就是用于auto_ptr和unique_ptr的策略,但unique_ptr的策略更严格。
  • ‘创建智能更高的指针,跟踪引用特定对象的智能指针数。这称为引用计数(reference counting)。例如,赋值时,计数加1,而指针过期时,计数减1。仅当最后一个指针过期时,才调用delete。这是shared_ptr采用的策略。
  • 当然同样的策略也适用于复制构造函数。

程序清单16.6 是一个不适合使用auto_ptr的示例。

//程序清单16.6  fowl.cpp
//fowl.cpp -- auto_ptr a poor choice
#include <iostream>
#include <string>
#include <memory>

int main()
{
    using namespace std;
    auto_ptr<string> films[5] = 
    {
        auto_ptr<string> (new string("Fowl Balls"));
        auto_ptr<string> (new string("Duck Walks"));
        auto_ptr<string> (new string("Chicken Runs"));
        auto_ptr<string> (new string("Turkey Errors"));
        auto_ptr<string> (new string("Goose Eggs"));
    };
    auto_ptr<string> pwin;
    pwin = films[2];      //films[2] loses ownership
    
    cout << "The nominees for best avian baseball film are\n";
    for(int i = 0; i < 5; i++)
    {
        cout << *film[i] << endl;
    }
    cout << "The winner is " << *pwin << "!\n";
    cin.get();
    return 0;
}

下面是该程序的输出:

The nominees for best avian baseball film are

Fowl Balls

Duck Walks

Segmentation fault (core dumped)

消息core dumped标明,错误地使用auto_ptr可能导致问题。这里的问题在于,将所有权从film[2]转让给pwin,这导致films[2]不再引用该字符串。如果使用shared_ptr来代替auto_ptr,则程序将正常运行。

2.3 unique_ptr为何优于auto_ptr

程序试图将一个unique_ptr赋给另一个时,如果源unique_ptr是个临时右值,编译器允许这样做;如果源unique_ptr将存在一段时间,编译器则禁止这样做。

using namespace std;
unique_ptr<string> pu1 (new string "Hi ho!");
unique_ptr<string> pu2;
pu2 = pu1;                                             //#1 not allowed
unique_ptr<string> pu3;
pu3 = unique_ptr<string> (new string "Yo!");           //#2 allowed

如果确实想执行#1的操作,要安全地重用这种指针,可给它重新赋值。C++有一个标准库函数std::move(),能够将一个unique_ptr赋给另一个:

using namespace std;
unique_ptr<string> ps1, ps2;
ps1 = demo("Uniquely special");
ps2 = move(ps1);
ps1 = demo(" and more");
cout << *ps2 << *ps1 << endl;

unique_ptr如何能够区分安全与不安全的用法呢?它使用了C++11新增的移动构造函数和右值引用。

unique_ptr还有一个优点,它有使用new[]和delete[]的版本:

std::unique_ptr<double[]> pda (new double[5]);

警告:使用new分配内存时,才能使用auto_ptr和shared_ptr,使用new[]分配内存时,不能使用它们。不使用new分配内存时,不能使用auto_ptr或shared_ptr;不使用new或new[]分配内存时,不能使用unique_ptr。

2.4 选择智能指针

应使用哪种智能指针?

如果程序要使用多个指向同一对象的指针,应选择shared_ptr。这样的情况包括:

有一个指针数组,并使用一些辅助指针来标识特定的元素,如最大的元素和最小的元素;

两个对象包含都指向第三个对象的指针;

STL容器包含指针。很多STL算法都支持复制和赋值操作,这些操作可用于shared_ptr,但不能用于unique_ptr(编译器发出警告)和unique_ptr(行为不确定)。

如果编译器没提供shared_ptr,可使用Boost库提供的shared_ptr。

如果程序不需要多个指向同一对象的指针,则可使用unique_ptr。如果函数使用new分配内存,并返回指向该内存的指针,将其返回类型声明为unique_ptr是不错的选择。

可将unique_ptr存储到STL容器中,只要不调用将一个unique_ptr复制或赋值给另一个的方法或算法(如sort())。

unique_ptr<int> make_int(int n)
{
    return unique_ptr<int>(new int(n));
}
void show(unique_ptr<int>& pi)   //pass by reference
{
    cout << *pi << ' ';
}

int main()
{
    //...
    vector<unique_ptr<int> >vp(size);
    for(int i = 0; i < vp.size(); i++)
        vp[i] = make_int(rand()%1000);
    vp.push_back(make_int(rand()%1000));
    for_each(vp.begin(), vp.end(), show);
    //...
}

如果按值而不是按引用给show()传递对象,for_each()语句将非法,因为这将导致使用一个来自vp的非临时unique_ptr初始化pi,而这是不允许的,编译器将发现错误使用unique_ptr的意图。

在unique_ptr为右值时,可将其赋给shared_ptr,这与将一个unique_ptr赋给另一个需要满足的条件相同:

unique_ptr<int> pup(make_int(rand()%1000));   //ok
shared_ptr<int> spp(pup);                     //not allowed, pup an 左值
shared_ptr<int> spr(make_int(rand()%1000));   //ok

模板shared_ptr包含一个显式构造函数,可用于将右值unique_ptr转换为shared_ptr。

在满足unique_ptr要求的条件时,也可使用auto_ptr,但unique_ptr是更好的选择。如果编译器没有提供unique_ptr,可考虑使用Boost库提供的scoped_ptr,它与unique_ptr类似。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值