智能指针:
智能指针的好处就是, 当智能指针过期时, 其析构函数将使用delete来释放内存. 因此, 如果将new返回的地址赋值给这些对象, 将无需记住稍后释放这些内存: 在只能指针过期时, 这些内存将自动被释放.
有三个只能指针模板: auto_ptr, unique_ptr 和 shared_ptr 都定义了类似指针的对象, 可以将new获得(直接或间接)的地址赋给这种对象.
要创建只能指针对象, 必须包含头文件memory, 然后使用通常的模板语法来实例化所需类型的指针, 例如:
template<class X> class auto_ptr {
public:
explicit auto_ptr(X * p = 0) throw();
...
}
// pd是一个指向double的auto_ptr
auto_ptr<double> pd(new double);
// ps是一个执行string的auto_ptr, 相当于string * ps;
auto_ptr<string> ps(new string);
其他两种智能指针的用法相同:
unique_ptr<double> pdu(new double);
shared_ptr<string> pss(new string);
智能指针模板位于名称空间std中. 下面我们来看一个完整的例子:
// smartptrs.cpp
#include <iostream>
#include <string>
#include <memory>
class Report
{
private:
std::string str;
public:
Report(const std::string s): str(s) {
std::cout << "Object created!" << std::endl;
}
~Report() {
std::cout << "Object deleted!\n";
}
void comment() const {std::cout << str << "\n"; }
};
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;
}
程序运行结果为:
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAzNfRHLGZkRGZkRfJ3bs92YsYTMfVmepNHL3FkaOhXVE9UerpXT41kMMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL5ATO2QzMyMjM5ATMwkTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
所有智能指针类都有一个explicit构造函数, 该构造函数将指针作为参数. 因此不需要自动将指针转换为智能指针对象:
shared_ptr<double> pd;
double * p_reg = new double;
// 这个是非法的转换, 因为p_reg是指针, 而pd是只能指针对象
pd = p_reg;
// 合法的
pd = shared_ptr<double>(p_reg);
// 非法的
shared_ptr<double> pshared = p_reg;
// 合法的
shared_ptr<double> pshared(p_rege=);
需要注意的一点: 对全部三种智能指针都应避免的一点:
string vacation("I wandered lonely as a cloud");
shared_ptr<string> pvac(&vacation);
pvac过期时, 程序将把delete运算符用于非堆内存, 这是错误的.
有关智能指针的注意事项:
先看下面的赋值语句:
auto_ptr<string> ps(new string("I reigned lonely as a cloud"));
auto_ptr<string> vocation;
vocation = ps;
如果ps和vocation是常规指针, 则两个指针将指向同一个string对象, 这是不能接受的, 因为程序将试图删除同一个对象两次--一次是ps过期时, 另一次是vocation过期时, 为避免这种问题, 有以下几种方法:
1. 定义赋值运算符, 使之执行深复制. 这样两个指针将指向不同的对象, 其中一个对象是另一个对象的副本.
2. 建立所有权(ownership)概念, 对于特定的对象, 只能有一个只能指针可拥有它, 这样只有拥有对象的只能指针的构造函数会删除该对象, 然后, 让赋值操作转让所有权. 这就是auto_ptr和unique_ptr的策略, 但unique_ptr的策略更严格.
3. 创建智能更高的指针, 跟踪引用特定对象的智能指针数, 称之为引用计数. 例如: 赋值时, 计数将加1, 而指针过期时, 计数将减1. 仅当最后一个指针过期时, 才调用delete. 这是 shared_ptr 采用的策略.
来看一个完整的例子:
// fowl.cpp, 智能指针的例子
#include <iostream>
#include <string>
#include <memory>
int main()
{
using namespace std;
auto_ptr<string> films[5] = {
auto_ptr<string> (new string("First word")),
auto_ptr<string> (new string("Second word")),
auto_ptr<string> (new string("Third word")),
auto_ptr<string> (new string("Fourth word")),
auto_ptr<string> (new string("Fifth word"))
};
auto_ptr<string> pwin;
pwin = films[2];
cout << "The nominees for best avian baseball film are \n";
for(int i = 0; i < 5; i++)
cout << *films[i] << endl;
cout << "The winner is " << *pwin << endl;
cin.get();
return 0;
}
程序运行结果为:
请看下面的语句:
auto_ptr<string> p1(new string("auto"));
auto_ptr<string> p2;
p2 = p1;
在第三行代码中, p2接管string对象的所有权后, p1的所有权将被剥夺. 但如果程序随后试图使用p1, 这件更引起异常, 因为p1不再指向有效的数据. 这也正是上面的fowl.cpp运行出错的原因.
如果我们换成unique_ptr, 会有不同的效果:
unique_ptr<string> p3(new string("auto"));
unique_ptr<string> p4;
p4 = p3;
编译器会认为上面第三行非法, 这样就避免了p3不再指向有效数据的问题. 因此unique_ptr比 auto_ptr更安全.
但是注意一点: 程序试图将一个unique_ptr赋给另一个时, 如果原unique_ptr是一个临时右值, 则编译器允许这样使用, 但如果原unique_ptr将存在一段时间, 编译器将禁止这样做. 例如:
using namespace std;
unique_ptr<string> pu1(new string("Hello"));
unique_ptr<string> pu2;
// 非法的
pu2 = pu1;
unique_ptr<string> pu3;
// 合法的.
pu3 = unique_ptr<string>(new string("world"));
如果我们一定要使用上述的pu2 = pu1; 这种操作. 仅当以非智能的方式使用遗弃的智能指针(如接触引用时), 这种赋值才不安全. 要安全地重用这种指针, 可以给它赋新值. C++提供了一个标准库函数std::move(), 让我们能够讲一个unique_ptr赋给另一个. 来看一个例子:
using namespace std;
unique_ptr<string> demo(const char * s)
{
unique_ptr<string> temp (new string(s));
return temp;
}
unique_ptr<string> ps1, ps2;
ps1 = demo("Uniquely special");
ps2 = move(ps1);
ps1 = demo(" and more");
cout << *ps2 << *ps1 << endl;
相比于auto_ptr, unique_ptr还有另一个优点. 它有一个用于数组的变体. 注意, 必须将delete和new配对使用, 将delete[]和new[]配对使用. 模版auto_ptr使用delete而不是delete[], 因此只能与new一起使用, 而不能与new[]一起使用. 但unique_ptr有使用new[]和delete[]的版本:
std::unique_ptr<double[]> pda(new double[5]);
如何选择智能指针:
1. 如果程序要使用多个指向同一个对象的指针, 应选择shared_ptr.
2. 如果程序不需要多个指向同一个对象的指针, 则可以使用unique_ptr. 如果函数使用new分配内存, 并返回指向该内存的指针, 推荐使用unique_ptr.
3. 在unique_ptr为右值时, 可将其赋给shared_ptr.