了解 C++ 中的智能指针
本文将讨论 C++ 中的智能指针、它们如何防止内存泄漏、智能指针的类型以及我们应该使用它们的情况。
用 C++ 实现智能指针
我们可以将 C++ 中的智能指针定义为一个类模板,我们可以使用它来维护原始原始指针。我们的智能指针类包含一个指针变量来保存我们的原始指针、析构函数和运算符覆盖方法。
但是,我们不限于仅包括这些字段和方法。我们可以根据需要添加我们的方法。
我们使用这个类来处理指针,而不是直接处理原始指针。除了在 C++ 中为智能指针定义自定义类,我们还可以使用标准 C++ 库。
让我们为 C++ 智能指针实现定义一个自定义类。
#include<iostream>
using namespace std;
template<class T>
class CustomSmartPointer
{
T *data;
public:
explicit CustomSmartPointer (T *ptr = NULL) // Constructor to assign pointer
{
data = ptr;
}
~CustomSmartPointer() // Destructor that deallocated memory
{
delete data;
}
// We overload * and -> operators
T *operator ->()
{
return data;
}
T &operator *()
{
return *data;
}
};
int main()
{
CustomSmartPointer<int> myPtr(new int());
*myPtr = 100;
cout<<*myPtr<<endl;
// After executing above statement, memory allocated to myPtr is deallocated.
return 0;
}
输出:
stark@stark:~/eclipse-workspace/smart_pointer$ g++ custom_smart_prt.cc
stark@stark:~/eclipse-workspace/smart_pointer$ ./a.out
100
使用智能指针析构函数防止 C++ 中的内存泄漏
析构函数的存在使我们的智能指针类与众不同,并将其与原始指针区分开来。我们使用 C++ 智能指针析构函数来释放分配给我们指针的内存。
当类对象超出范围时,会自动调用析构函数并释放分配的内存。如果我们已经自动化了内存释放过程,我们就不必担心我们可能忘记释放的指针导致的资源泄漏。
智能指针析构函数的作用类似于 C++ 中的自动垃圾收集器,类似于 Java 和 C#。
让我们看一个由原始指针引起的资源泄漏的例子。
#include<iostream>
using namespace std;
class student
{
private:
string name;
int marks;
public:
void setMarks(int m)
{
marks = m;
}
void setName(string n)
{
name = n;
}
int getMarks()
{
return marks;
}
string getName()
{
return name;
}
};
int main()
{
while(true)
{
student * p = new student;
*p->name = "Stark";
*p->marks = 100;
}
return 0;
}
如我们所见,我们正在无限循环中初始化学生类型的指针。我们还没有释放指针内存,所以这将继续分配资源,直到所有内存都被占用。
我们不应该长时间执行这个程序。否则,我们的系统可能会因为内存溢出而挂起。
但是,如果我们使用智能指针,内存将在每次循环后自动释放。因此,一次只占用一个指针内存。
C++ 中智能指针的类型
我们在 C++ 中有三种不同类型的智能指针。这些是在 C++ 库中为智能指针实现的类型。
unique_ptr
这种智能指针类型让我们只为底层原始指针分配一个用户。这意味着我们不能将相同的底层指针分配给两个或更多对象。
我们应该默认使用这种类型的指针,直到我们可以共享内存。
shared_ptr
顾名思义,我们可以使用这个智能指针将多个所有者分配给一个原始指针。这使用参考计数器来跟踪受让人的数量。
weak_ptr
这种智能指针类型与 shared_ptr
非常相似,只是它不参与引用计数器。这并不意味着它没有引用计数器,但它的引用不被计数。
相反,我们使用它的引用计数器来计算其他共享引用。当我们不需要严格控制所有权时,我们可以使用这个指针。
它提供对一个或多个 shared_ptr
对象的访问。但是,它有一个优势,因为它消除了死锁的可能性。
让我们看一个 C++ 智能指针实现。
#include<iostream>
#include<memory>
using namespace std;
class student
{
private:
string name;
int marks;
public:
void setMarks(int m)
{
marks = m;
}
void setName(string n)
{
name = n;
}
int getMarks()
{
return marks;
}
string getName()
{
return name;
}
};
int main()
{
unique_ptr<student> ptr1 (new student);
ptr1->setName("Stark");
ptr1->setMarks(100);
cout<<"unique_ptr output >>\n";
cout<<ptr1->getName()<<" : "<<ptr1->getMarks()<<"\n";
cout<<"shared_ptr output >> \n";
shared_ptr<student> ptr2 (new student);
ptr2->setName("Tony");
ptr2->setMarks(99);
cout<<ptr2->getName()<<" : "<<ptr2->getMarks()<<"\n";
shared_ptr<student> ptr22;
ptr22 = ptr2;
cout<<ptr22->getName()<<" : "<<ptr22->getMarks()<<"\n";
cout<<"Reference count of shared_ptr: "<<ptr2.use_count()<<endl;
auto ptr = make_shared<student> ();
ptr->setName("Jarvis");
ptr->setMarks(98);
cout<<"Weak pointer output >>"<<endl;
weak_ptr<student> ptr3;
ptr3 = ptr;
cout<<"Reference count of weak_ptr: "<<ptr3.use_count()<<endl;
shared_ptr<student> ref = ptr3.lock();
cout<<ref->getName()<<" : "<<ref->getMarks()<<"\n";
return 0;
}
输出:
stark@stark:~/eclipse-workspace/smart_pointer$ g++ types_smart.cc
stark@stark:~/eclipse-workspace/smart_pointer$ ./a.out
unique_ptr output >>
Stark : 100
shared_ptr output >>
Tony : 99
Tony : 99
Reference count of shared_ptr: 2
Weak pointer output >>
Reference count of weak_ptr: 1
Jarvis : 98
需要注意的是,弱指针的引用计数是 1,因为它不参与引用计数。
何时在 C++ 中使用智能指针
我们不应该对所有事情都使用智能指针。由于我们为智能指针使用类模板,它们通常会降低性能并增加程序的复杂性。
我们应该将原始指针用于性能关键的应用程序和小程序作为最佳实践。但是,我们应该始终在指针资源使用结束后释放它们。
如果我们的代码很大并且很难手动为每个指针释放内存,我们应该使用智能指针。如果应用程序不是性能关键,我们应该更喜欢使用智能指针。
如果我们需要处理异常、引用计数、释放时间执行,我们应该使用智能指针。