天天看点

C++ Primer Plus 书之--C++ 智能指针 auto_ptr, shared_ptr, unique_ptr智能指针:

智能指针:

智能指针的好处就是, 当智能指针过期时, 其析构函数将使用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;
}
           

程序运行结果为:

C++ Primer Plus 书之--C++ 智能指针 auto_ptr, shared_ptr, unique_ptr智能指针:

所有智能指针类都有一个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;
}
           

程序运行结果为:

C++ Primer Plus 书之--C++ 智能指针 auto_ptr, shared_ptr, unique_ptr智能指针:

请看下面的语句:

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.

继续阅读