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

457

被折叠的 条评论
为什么被折叠?



