智能指针是行为类似于指针的类对象,但是这种对象还有其他功能。常用的auto_ptr,unique_ptr和shared_ptr三种智能指针模板,可帮助我们管理动态内存分配。
简单看看下面的例子:
void remodel(string &str)
{
string *ps = new string(str);
...
str = *ps;
return;
}
这个函数是存在缺陷的。每当调用时,该函数都会分配内存,但从来不收回,从而导致内存泄露。解决的方法就是再return语句之前加上delete ps;但是有时可能忘记,有时可能被注释了。简单分析这个函数,当remodel()这样的函数终止,本地变量都将从栈内存中中删除,因此指针ps占据的内存将被释放。如果ps指向的内存也被释放多好啊。如果ps有一个析构函数,该析构函数将在ps过期时释放它指向的内存。但问题是ps只是一个常规的指针,不是有析构函数的类对象。
如果它是对象,则可以在对象过期时,让他的析构函数删除指向的内存,这正是智能指针背后的原理。
auto_ptr是c++98提供的解决方案,c++1已经抛弃,并提供了另外的两种方法。
使用智能指针
修改上面的remodel()函数:
1.包含头文件memory
2.修改指针
3.删除delete语句
void remodel(string &str)
{
auto_ptr<string> ps (new string(str));
//...
str = *ps;
//delte ps;no longer needed
return;
}
智能指针的构造:
所有的智能指针类都有一个explicit构造函数,该构造函数将指针作为参数。不需要自动将指针转换成智能指针对象:
shared_ptr<double>pd;
double *p_reg = new double;
pd = p_reg;//not allowed(implicit conversion)
pd = shared_ptr<double>(p_reg);//allowed(explicit conversion)
shared_ptr<double>pshared = p_reg;//not allowed(implicit conversion)
shared_ptr<double>pshared(p_reg);//allowed(explicit conversion)
使用智能指针应该避免一点:
string info1("test");
shared_ptr<string> pinfo(&info1);
pinfo过期时,程序将把delete运算符用于非堆内存,这是错误的。
有关使用智能指针的注意事项:
1.智能指针的赋值
auto_ptr<string> pinfo(new string("test"));
auto_ptr<string> pp;
pp = pinfo;
如果pinfo和pp是常规指针,则两个指针将指向同一个string对象。上面的程序运行阶段崩溃,对象的所有权转移到了pp,当程序试图删除同一个对象两次,一次是pinfo过期,另一次是pp过期,要避免这种问题,方法很多种:
- 定义赋值运算符,执行深复制。这样两个指针指向不同的对象,其中一个对象是另一个对象的副本。
- 建立所有权(ownership)概念,对于特定的对象,只能有一个智能指针可以拥有它,这样只有拥有该对象的智能指针的析构函数可以删除该对象。然后,让赋值操作转让所有权。这是用于auto_ptr和unique_ptr的策略,unique_ptr的策略更严格。
- 创建更高的指针,跟踪引用特定对象的智能指针数。这称为引用计数。仅当最后一个指针过期,才调用delete,这是shared_ptr的策略。
因此上述的代码改成shared_ptr是没有问题的。
auto_ptr和unique_ptr对比:
上述的代码中,pp接管了string对象的所有权后,pinfo的所有权被剥夺,但如果程序随后试图使用pinfo,这将出现问题,因为pinfo不再指向有效的数据。
使用将auto_ptr改成unique_ptr:
unique_ptr<string> pinfo(new string("test"));
unique_ptr<string> pp;
pp = pinfo;//error
编译器认为pp = pinfo非法。避免了pinfo不再指向有效数据的问题。
因此unique_ptr比auto_ptr更安全(编译阶段错误比潜在的程序崩溃更安全)。
但有时候将一个智能指针赋给另一个并不会留下危险的悬挂指针。比如下面的程序:
unique_ptr<string> demo(const char*s)
{
unique_ptr<string>tmp(new string(s));
return tmp;
}
unique_ptr<string>p3;
p3 = demo("dadada");
demo()函数返回一个临时unique_ptr,然后p3接管了原本返回unique_ptr所有的对象,而返回的unique_ptr被销毁。这没问题,因为p3拥有string对象的所有权。但这里的另外一个好处就是,demo()返回的临时unique_ptr很快被销毁,没有机会使用它来访问无效的数据。换句话说,没有理由禁止这种赋值。神奇的是编译器确实允许这种赋值。
总之,程序试图将一个unique_ptr赋给另一个,如果unique_ptr是临时右值,编译器允许这样做;如果源unique_ptr将存在一段时间,编译器将禁止这样做。
unique_ptr<string>p3;
p3 = unique_ptr<string>(new string("ahhaha"));
上面的代码不会留下悬挂的unique_ptr指针,因为调用了unique_ptr的构造函数,该构造函数创建的临时对象在其所有权转让给p3后被销毁。
相对于auto_ptr,unique_ptr还有一个优点就是可以用于数组的变体。模板auto_ptr使用delete而不是delete[],因此只能和new一起用,而不能和new[]一起用。但unique_ptr有使用new[]和delete[]的版本:
unique_ptr<double[]>pda(new double(5));
注:使用new分配内存时,才能使用auto_ptr和shared_ptr,使用new[]分配内存,使用unique_ptr。
代码实现:
//Shared_ptr.h
#ifndef SHARED_PTR_H_
#define SHARED_PTR_H_
#include<iostream>
using namespace std;
template<typename T>
class Shared_ptr
{
private:
T*m_ptr;
size_t*m_count;
public:
//构造函数
Shared_ptr() :m_ptr(nullptr), m_count(new size_t) {}
Shared_ptr(T*ptr) :m_ptr(ptr), m_count(new size_t) { *m_count = 1; }
//拷贝构造函数
Shared_ptr(const Shared_ptr&ptr)
{
m_ptr = ptr.m_ptr;
m_count = ptr.m_count;
++(*m_count);
}
//拷贝赋值函数
void operator=(const Shared_ptr&ptr)
{
Shared_ptr(move(ptr));
}
//移动构造函数
Shared_ptr(Shared_ptr&&ptr) :m_ptr(ptr.m_ptr), m_count(ptr.m_count) { ++(*m_count); }
//移动赋值函数
void operator=(const Shared_ptr&&ptr) { Shared_ptr(move(ptr)); }
//箭头运算符
T* operator->() { return m_ptr; }
//解引用运算符
T& operator*() { return *m_ptr; }
//重载bool运算符
operator bool() { return m_ptr == nullptr; }
T*get() { return m_ptr; }
size_t use_count() { return *m_count; }
bool unique() { return *m_count == 1; }
void swap(Shared_ptr&ptr)
{
swap(*this, ptr);
}
//析构函数
~Shared_ptr()
{
--(*m_count);
if (*m_count == 0)
{
delete m_ptr;
delete m_count;
m_ptr = nullptr;
m_count = nullptr;
}
}
};
class Person
{
private:
int m_Age;
string m_Name;
public:
Person(int age, string name)
{
cout << "Person(int age,string name):" << age << " " << name << endl;
m_Age = age;
m_Name = name;
}
~Person()
{
cout << "~Person()" << m_Age << " " << m_Name << endl;
}
//显示人物信息
void showInfo()
{
cout << "name = " << m_Name << ", age = " << m_Age << endl;
}
};
#endif
//mian.cpp
void mian()
{
Shared_ptr<Person> p1 = Shared_ptr<Person>(new Person(5,"hhh"));
(p1.get())->showInfo();
p1->showInfo();
(*p1).showInfo();
cout << p1.operator bool() << endl;
cout << p1.use_count() << endl;
cout << p1.unique() << endl;
Shared_ptr<Person> p2= Shared_ptr<Person>(p1);
cout << p1.operator bool() << endl;
cout << p1.use_count() << endl;
cout << p1.unique() << endl;
cout << p2.operator bool() << endl;
cout << p2.use_count() << endl;
cout << p2.unique() << endl;
Shared_ptr<Person> p3 = getInst();
int n = p3.use_count();
cout << n << endl;
int m=getInst().use_count();
cout << m << endl;
}
参考:
C++移动构造函数以及move语句简单介绍
https://www.cnblogs.com/qingergege/p/7607089.html
四种智能指针的用法和原理
https://blog.youkuaiyun.com/sinat_36118270/article/details/69061348
c++智能指针-知乎
https://zhuanlan.zhihu.com/p/54078587