智能指针的引入,是为了解决普通指针在使用过程中存在的一些问题:其中内存泄漏以及空悬指针是最主要的问题。
正常使用普通指针,我们需要 new
分配内存,使用 delete
释放资源,一旦项目很庞大,尤其是在多个地方共享同一个指针时,产生内存泄漏的风险很大,且需要更多的代码来管理指针。
下面举一个具体实例,比如两个对象共享同一个指针,此时对于该指针在什么时候释放需要更多地代码来判断,以防止内存泄漏与访问空悬指针。
#include
#include
class Person
{
public:
string name;
Person* child;
Person(const string& n, Person* c = nullptr) : name(n), child(c) {
}
~Person() {
std::cout << "delete" << name << std::endl;
}
}
int main()
{
Person* son = new Person("hhhcbw");
Person father("c", son);
Person mother("z", son);
delete son;
std::cout << father.child->name << std::endl; // ERROR: ask hanging pointer
}
为了解决普通指针的痛点,引入智能指针。
shared_ptr
从字面就可以看出,该智能指针类主要用于共享资源,其能保证当最后一个对对象的引用被删除后,对象本身被删除(包括一些内存与资源的释放)。
使用 shared_ptr
与使用普通指针差不多。可以赋值,拷贝以及比较 shared_ptr
,也可以使用操作符 *
和 ->
来访问指针指向的对象。举一个例子:
#include
#include
#include
#include
using namespace std;
int main()
{
// two shared pointers representing two persons by their name
shared_ptr<string> pNico(new string("nico"));
shared_ptr<string> pJutta(new string("jutta"));
// capitalize person names
(*pNico)[0] = ’N’;
pJutta->replace(0,1,"J");
// put them multiple times in a container
vector<shared_ptr<string>> whoMadeCoffee;
whoMadeCoffee.push_back(pJutta);
whoMadeCoffee.push_back(pJutta);
whoMadeCoffee.push_back(pNico);
whoMadeCoffee.push_back(pJutta);
whoMadeCoffee.push_back(pNico);
// print all elements
for (auto ptr : whoMadeCoffee) {
cout << *ptr << " ";
}
cout << endl;
// overwrite a name again
*pNico = "Nicolai";
// print all elements again
for (auto ptr : whoMadeCoffee) {
cout << *ptr << " ";
}
cout << endl;
// print some internal data
cout << "use_count: " << whoMadeCoffee[0].use_count() << endl;
}
Jutta Jutta Nico Jutta Nico
Jutta Jutta Nicolai Jutta Nicolai
use_count: 4
shared_ptr
类定义在
里,需要注意的是,shared_ptr
的使用一个指针作为单独参数的构造函数是显式的(explicit),因此不能使用赋值符号,来将普通指针赋值给 shared_ptr
:
shared_ptr<string> pNico = new string("nico"); // ERROR
shared_ptr<string> pNico{new string("nico")}; // OK
也可以使用函数 make_shared()
来创建 shared_ptr
,且这样更快且更安全:因为相比于前面的初始化的两次分配内存(一次给对象,一次给共享指针的共享数据),使用函数 make_shared()
只需要一次分配内存,完成两个步骤。
注意,尽量不要对一个已有普通指针,创建共享指针,如:
string* pNico = new string("nico");
shared_ptr<string> spNico(pNico);
如果pNico被设为nullptr,spNico.use_count()=1
且字符串未被释放,正常输出 nico
。
但如果spNico被设为nullptr,此时字符串被释放,但pNico还保存该地址!!
可以先声明一个共享指针,然后给该共享指针分配一个新的指针。当然,不能使用赋值操作,要使用 reset()
方法:
shared_ptr<string> pNico4;
pNico4 = new string("nico"); // ERROR: no assignment for ordinary pointers
pNico4.reset(new string("nico")); // OK
与普通指针类似,使用 *
与 ->
:
(*pNico)[0] = ’N’;
pJutta->replace(0,1,"J");
use_count()
表示当前拥有该对象的所有共享指针的数量,当一个共享指针被删除后,use_count()-1
,反之,use_count()+1
。
上面例子中,pJutta 本身算一个,容器 vector
里还有三个,所有 use_count() = 4
当最后一个拥有者被删除后,共享指针为对象调用 delete
进行内存和资源的释放。这不一定在作用域结束处发生,比如上面的例子中,当给 pNico
赋值 nullptr
且在将 vector
resize 为 2, 也会导致最后一个拥有者被删除,以至调用 delete
。
我们甚至可以自定义 Deleter,例如在删除引用对象前输出一条信息:
shared_ptr<string> pNico(new string("nico"),
[](string* p) {
cout << "delete " << *p << endl;
delete p;
});
...
pNico = nullptr; // pNico does not refer to the string any longer
whoMadeCoffee.resize(2); // all copies of the string in pNico are destroyed
这里传入一个lambda表达式,作为 shared_ptr
构造函数的第二个参数,当然对于任何可调用的对象都是可以的,比如函数与重载了()
运算符的类与std::function
,比如下面的代码就是重载了 ()
运算符的类:
#include
#include // for ofstream
#include // for shared_ptr
#include // for remove()
class FileDeleter
{
private:
std::string filename;
public:
FileDeleter (const std::string& fn)
: filename(fn) {
}
void operator () (std::ofstream* fp) {
fp->close(); // close.file
std::remove(filename.c_str()); // delete file
}
};
int main()
{
// create and open temporary file:
std::shared_ptr<std::ofstream> fp(new std::ofstream("tmpfile.txt"),
FileDeleter("tmpfile.txt"));
...
}
shared_ptr
提供的默认deleter调用 delete
而不是 delete[]
。这意味着默认 deleter只有在共享指针拥有的是一个单独由new创建的对象才有效。需要注意的是,给一个数组创建共享指针是可能的,但是是错误的:
std::shared_ptr<int> p(new int[10]); // ERROR, but compiles
所以,如果要使用 new[]
来创建一个对象数组,需要定义自己的 deleter。可以传入一个函数、function object或lambda,在内部调用 delete[]
,例如:
std::shared_ptr<int> p(new int[10],
[](int* p) {
delete[] p;
});
也可以使用提供给 unique_ptr
的官方helper,其内部调用 delele[]
std::shared_ptr<int> p(new int[10],
std::default_delete<int[]>());
当然,unique_ptr
与 shared_ptr
在数组的处理上有一定的区别,更详细地会在 unique_ptr
讲解
std::unique_ptr<int[]> p(new int[10]); // OK
std::shared_ptr<int[]> p(new int[10]); // ERROR: does not compile
std::unique_ptr<int,void(*)(int*)> p(new int[10],
[](int* p) {
delete[] p;
});
shared_pr
不提供操作符 []
。