一 什么是智能指针
由于 C++ 语言没有自动内存回收机制,程序员每次 new 出来的内存都要手动 delete。程序员忘记 delete,流程太复杂,最终导致没有 delete,异常导致程序过早退出,没有执行 delete 的情况并不罕见。智能指针主要用于管理在堆上分配的内存,它将普通的指针封装为一个栈对象。当栈对象的生存周期结束后,会在析构函数中释放掉申请的内存,从而防止内存泄漏,智能指针的作用原理就是在函数结束时自动释放内存空间,不需要手动释放内存空间。
例如在函数中new申请空间之后,会调用delete进行删除;
void test()
{
testclass* ptr = new testclass; // testclass是一个类或者结构
// 实例对象操作
// ...
// 执行结束
delete ptr;
}
但是在异常情况下可能执行不到delete,函数就已经返回,因此无法将申请的空间进行释放,导致内存泄漏。
void someFunction()
{
testclass* ptr = new testclass; // testclass是一个类或者结构
int findnum;
std::cout << "Enter an integer: ";
std::cin >> findnum;
vector<int> testvec={1,2,3,4,5,6,7,8,9}
for(auto num:testvec)
{
if(findnum==num)
return; //提前终止没有释放指针的内存
}
// ...
delete ptr;
}
提出一种解决这种问题的方法,然后,你可能想到了类:类内部存储指针,然后在析构函数中销毁该指针。类可以实现资源的自动管理。其好处是,只要类局部变量(分配在栈上)超出其作用域(不论其是如何离开的),其析构函数一定会被执行,那么管理的内存也将一定得到销毁。基于这样的想法,我们实现了一个简单的智能指针类:
using namespace std;
template<typename T>
class Auto_ptr1
{
public:
Auto_ptr1(T* ptr = nullptr) :
m_ptr{ ptr }
{}
virtual ~Auto_ptr1()
{
delete m_ptr;
}
T& operator*() { return *m_ptr; }
T* operator->() { return m_ptr; }
private:
T* m_ptr;
};
class Resource
{
public:
Resource() { cout << "Resource acquired!" << endl; }
virtual ~Resource() { cout << "Resource destoryed!" << endl; }
};
int main()
{
{
Auto_ptr1<Resource> res(new Resource);
}
cin.ignore(10);
return 0;
}
创建了一个关于智能指针的类变量,然后将申请的动态内存交给它保存,由于类变量工作在局部空间,将其离开时就会调用析构函数。
二 C++常用的智能指针
对于编译器来说,智能指针实际上是一个栈对象,调用的时候将对象放入栈中,并非指针类型,在栈对象生命期即将结束时,智能指针通过析构函数释放有它管理的堆内存。
智能指针的方法
所有智能指针都重载了“operator->”操作符,直接返回对象的引用,用以操作对象。
使用“.”操作符访问智能指针原来的方法。
访问智能指针包含的裸指针则可以用 get() 函数。由于智能指针是一个对象,所以if (my_smart_object)永远为真,要判断智能指针的裸指针是否为空,需要这样判断:if (my_smart_object.get())。
智能指针包含了 reset() 方法,如果不传递参数(或者传递 NULL),则智能指针会释放当前管理的内存。如果传递一个对象,则智能指针会释放当前对象,来管理新传入的对象。
1.std::auto_ptr对STL不兼容,因为STL的对象在进行复制时,就是进行复制,而不是移动语义。所以实际上,在std::auto_ptr在C++11中已经被弃用了,并且在C++17中被移除标准库。但是可以借助它来理解智能指针。
创建需要测试的类对象
class Mytest {
public:
Mytest(int num=0): number(num)
{
std::cout << "Construct Mytest:" << number << std::endl;
}
~Mytest() {
std::cout << "~Discontruct Mytest: " << number << std::endl;
}
void PrintSomething() {
std::cout << "mynumber: "<<number <<" "<< teststring.c_str() << std::endl;
}
std::string teststring;
private:
int number;
};
对std::auto_ptr进行测试
void TestAutoPtr() {
std::auto_ptr<Mytest> ptr1(new Mytest());
if (ptr1.get()) { // 判断智能指针是否为空
ptr1->PrintSomething(); // 使用 operator-> 调用智能指针对象中的函数
ptr1.get()->teststring = "Automation"; // 使用 get() 返回裸指针,然后给内部对象赋值
ptr1->PrintSomething(); // 再次打印,表明上述赋值成功
(*ptr1).teststring += " pointer"; // 使用 operator* 返回智能指针内部对象,然后用“.”调用智能指针对象中的函数
ptr1->PrintSomething(); // 再次打印,表明上述赋值成功
}
}
执行输出
这里可以看出auto_ptr的使用,但是它有致命的缺点,导致不能推广使用,就是在设计这个智能指针时,没有完善它的复制功能,当实例对象1复制到实例对象2之后,会导致实例对象1消失,再次调用则会报错。
void TestAutoPtr2() {
std::auto_ptr<Mytest> ptr1(new Mytest(3));
if (ptr1.get()) {
std::auto_ptr<Mytest> ptr2; // 创建一个新的 ptr2 对象
ptr2 = ptr1; // 复制旧的 ptr 给 ptr2
ptr2->PrintSomething(); // 输出信息,复制成功
ptr1->PrintSomething(); // 崩溃
}
}
2.std::unique_ptr
std::unique_ptr是std::auto_ptr的替代品,其用于不能被多个实例共享的内存管理。这就是说,仅有一个实例拥有内存所有权。
他解决的问题主要是因为auto_ptr不能复制,因此unique_ptr就直接禁止复制操作,使用的时候会报错。在操作时完全使用move语义。
void TestuniquePtr1() {
std::unique_ptr<Mytest> ptr1(new Mytest(5));
if (ptr1.get()) {
std::unique_ptr<Mytest> ptr2; // 创建一个新的 ptr2 对象
std::cout << "ptr1 is " << (static_cast<bool>(ptr1) ? "not null\n" : "null\n");
std::cout << "ptr2 is " << (static_cast<bool>(ptr2) ? "not null\n" : "null\n");
ptr2 = move(ptr1); // 复制旧的 ptr 给 ptr2
std::cout << "ptr1 is " << (static_cast<bool>(ptr1) ? "not null\n" : "null\n");
std::cout << "ptr2 is " << (static_cast<bool>(ptr2) ? "not null\n" : "null\n");
ptr2->PrintSomething(); // 输出信息,复制成功
//ptr1->PrintSomething(); // 崩溃
}
}
3.std::shared_ptr
std::shared_ptr与std::unique_ptr类似。要创建std::shared_ptr对象,可以使用make_shared()函数(C++11是支持的,貌似制定这个标准的人忘了make_unique(),所以在C++14追加了)。
std::shared_ptr与std::unique_ptr的主要区别在于前者是使用引用计数的智能指针。引用计数的智能指针可以跟踪引用同一个真实指针对象的智能指针实例的数目。这意味着,可以有多个std::shared_ptr实例可以指向同一块动态分配的内存,当最后一个引用对象离开其作用域时,才会释放这块内存。
应用
void TestsharedPtr1() {
Mytest* res = new Mytest(6);
std::shared_ptr<Mytest> ptr1(res);
{
std::shared_ptr<Mytest> ptr2(res); // create ptr2 directly from res (instead of ptr1)
std::cout << "Killing one shared pointer\n";
} // ptr2 goes out of scope here, and the allocated Resource is destroyed
std::cout << "Killing another shared pointer\n";
}
爆出异常因为,当两个指针指向同一个资源时,他们互相不知道对方的存在,当前一个释放之后后一个便无法进行操作。
解决方法
void TestsharedPtr1() {
auto ptr1 = std::make_shared<Mytest>();
{
auto ptr2 = ptr1; // create ptr2 using copy initialization of ptr1
std::cout << "Killing one shared pointer\n";
} // ptr2 goes out of scope here, but nothing happens
std::cout << "Killing another shared pointer\n";
}
使用make_shared进行复制创建,这样会产生引用计数,不同的shared_ptr之间会知道有其它的也指向这个资源。