Why do we need smart pointer and how to implement it.

时间:2022-07-28 20:07:10

  Here are two simple questions.

  Problem A

 #include <string>
include <iostream>
using namespace std; class vehicle
{
public:
vehicle(const string& name);
virtual ~vehicle(){} void PrintOwnerInfo(); private:
string driver_name_;
}; vehicle::vehicle(const string& name):
driver_name_(name){
} void vehicle::PrintOwnerInfo(){
cout<<driver_name_<<endl;
} int main()
{
string driver_name = "Zhou You";
vehicle* pbicycle = new vehicle(driver_name);
pbicycle->PrintOwnerInfo();
return ;
}

  Any problems in the first piece of code?In main function,I produce an vehicle object which is holden by "pbicycle",then I call PrintOwnerInfo to output the name of the driver.The point is I fail to delete the pointer pbicycle which holds an vehicle object,and this is the most common mistake new programmers always make(it calls memory leak).

  Look at another piece of code.Problem B

class A
{
public:
A();
A(int num);
~A(){} void PrintNum();
private:
int num_;
}; A::A():num_(){
} A::A(int num):
num_(num){
} void A::PrintNum(){
cout<<num_<<endl;
} int main()
{
A *pa1 = new A();
pa1->PrintNum(); A *pa2 = pa1;//pa2 point to the same object with pa1
pa2->PrintNum(); delete pa1
pa2->PrintNum();//error happens return ;
}

  In main function,I produce an object of class A,using pa1 to point to it.With a pointer of this object,I call PrintNum function to print the integer.Now that I have pa1,I want to create another pointer pa2 to point to the same object with pa1.The next thing I want to do is to delete pa1.Can I do that?Nooooo.With this inappropriate statement,I transform pa2 to a dangling pointer.The code above is pretty simple and easy,so we can understand it very quickly and find the drawbacks.But in real projects,such mistakes can be easily made and ignored.Is there any good solutions to avoid it?I mean we don't even need to care about these things.Actually this is part of memory management in C++,we can use smart pointer to help us to handle managing memory issue.

  In the vehicle instance,We can write main function with the smart pointer like this.

int main()
{
string driver_name = "John Zhou";
SPvehicle spbicycle = new vehicle(driver_name);
spbicycle->PrintOwnerInfo(); return ;
}

  "SPvehicle" is a smart pointer class for vehicle.I do not need to delete it When I finish using this pointer,because this pointer itself has the feature to release its object when it is not useful any more.So how to implement it? Basically,there are two points to take care.

1."spbicycle" should behave like an vehicle pointer does.It should support both dereferencing(*) operation and indirection(->) operation.

2.It should be able to release the object automatically.

  Check out the code below.

class SPvehicle
{
public:
  SPvehicle(){} 
SPvehicle(vehicle *pvehicle);
~SPvehicle(); vehicle& operator*(){
return *pobjvehicle_;
} vehicle* operator->(){
return pobjvehicle_;
}
private:
vehicle *pobjvehicle_;
}; SPvehicle::SPvehicle(vehicle *pvehicle):
pobjvehicle_(pvehicle){
} SPvehicle::~SPvehicle(){
delete pobjvehicle_;
}

  In the SPvehicle class,I overload 2 operators(*,->),then I can use it as a normal vehicle pointer.In the destructor,I delete pobjvehicle_ to release the memory space.

  Class SPvehicle gives us a solution to manage the object automatically,but still remains problem B unsolved.So how to handle the dangling pointer?The object it points to is released by the other pointer,but it is not informed,and this is the direct reason it becomes dangling.For an object,can we remember the amount of the pointer which point to it?With the amount,we are able to know the right time to release the object(the amount decreasing to 0).Let's try.

class RfCount
{
public:
RfCount(){}
RfCount(unsigned rfc):
rfc_(rfc){
} ~RfCount(){} unsigned AddRef(){
return ++rfc_;
} unsigned CutRef(){
return --rfc_;
} private:
unsigned rfc_;
}; class SPA
{
public:
SPA();
SPA(A *pa);
SPA(const SPA &spa);
~SPA(); A& operator*(){
return *pa_;
} A* operator->(){
return pa_;
} SPA& operator=(const SPA &spa); private:
RfCount* reference_count_;
A *pa_;
}; SPA::SPA():pa_(NULL),
reference_count_(new RfCount()){
cout<<"default SPA constructor."<<endl;
} SPA::SPA(A* pointera):
pa_(pointera),
reference_count_(new RfCount()){
cout<<"SPA constructor1 adds referencecount by 1."<<endl;
reference_count_->AddRef();
} SPA::SPA(const SPA& spa):
pa_(spa.pa_),
reference_count_(spa.reference_count_){
cout<<"SPA constructor2 adds referencecount by 1."<<endl;
reference_count_->AddRef();
} SPA::~SPA(){
cout<<"SPA destructor cut referencecount by 1."<<endl;
if(reference_count_->CutRef() == ){
cout<<"release the object."<<endl;
delete pa_;
}
} SPA& SPA::operator=(const SPA& spa){
if(this != &spa){
cout<<__FUNCTION__<<" "<<"refcount added by 1."<<endl;
reference_count_ = spa.reference_count_;
reference_count_->AddRef();
pa_ = spa.pa_;
}
return *this;
} void copypointer(SPA& spa){
SPA spa1;
spa1 = spa;
spa1->PrintNum();
} int main()
{
SPA spa1 = new A();
spa1->PrintNum();
copypointer(spa1);
spa1->PrintNum();
return ;
}

  In main function,I produce a smart pointer spa1 at first(reference count is initialized to 1),then use it to call member function of class A which can prove that I can use this smart pointer as a normal A pointer.The next step is to call copypointer() function and it takes spa1 as the argument.In copypointer(),I reproduce a local smart pointer spa2 which is assigned with spa1(reference count up to 2),during the process of assignment,the referencecount is increased to 2.After function copypointer() finishes its task,spa2 just goes out of scope,and SPA destructor is called.In SPA destructor, referencecount is reduced by 1.Back to main funtion,when main ends up,SPA destructor is called again,and reference count is reduced to 0,and that's exactly the time we need to release A pointer inside class SPA,because there are no pointers referencing this A object.

  OK,for problem A and B I post at the start of this article,we just figure out how to solve or avoid them,but the code look not cool.For problem A and B,we have to write 2 smart pointer classes respectively.so how about a generic class to implement the smart pointer.

class RfCount
{
public:
RfCount(){}
RfCount(unsigned rfc):
rfc_(rfc){
} ~RfCount(){} unsigned AddRef(){
return ++rfc_;
} unsigned CutRef(){
return --rfc_;
} private:
unsigned rfc_;
}; template <typename T>
class SP
{
public:
SP();
SP(T *pa);
SP(const SP<T>& sp);
~SP(); T& operator*(){
return *pt_;
} T* operator->(){
return pt_;
} SP& operator=(const SP &sp); unsigned AddRef(){
return reference_count_->AddRef();
} unsigned CutRef(){
return reference_count_->CutRef();
} private:
RfCount* reference_count_;
T *pt_;
}; template <typename T>
SP<T>::SP():pt_(NULL),
reference_count_(new RfCount()){
cout<<"default SP constructor."<<endl;
} template <typename T>
SP<T>::SP(T *pt):
pt_(pt),
reference_count_(new RfCount()){
cout<<"SP constructor1 adds referencecount by 1."<<endl;
reference_count_->AddRef();
} template <typename T>
SP<T>::SP(const SP<T>& sp):pt_(sp.pt_),
reference_count_(sp.reference_count_){
cout<<"SP constructor2 adds referencecount by 1."<<endl;
reference_count_->AddRef();
} template <typename T>
SP<T>::~SP(){
cout<<"SP destructor cut referencecount by 1."<<endl;
if(reference_count_->CutRef() == ){
cout<<"release the object."<<endl;
delete pt_;
}
} template <typename T>
SP<T>& SP<T>::operator=(const SP &sp){
if(this != &sp){
reference_count_ = sp.reference_count_;
cout<<__FUNCTION__<<" "<<"refcount added by 1."<<endl;
reference_count_->AddRef();
pt_ = sp.pt_;
}
return *this;
} void copypointer(SP<A>& spa){
SP<A> spa1;
spa1 = spa;
spa1->PrintNum();
} int main()
{
SP<A> spa1 = new A();
spa1->PrintNum();
copypointer(spa1);
spa1->PrintNum();
return ;
}

  Using template,we can write a generic smart pointer class.

  //词汇拙计啊,乃们就将就看吧,有啥问题给俺指正出来呀。