2024-07-12
한어Русский языкEnglishFrançaisIndonesianSanskrit日本語DeutschPortuguêsΕλληνικάespañolItalianoSuomalainenLatina
Disadvantages of naked pointers:
delete
Releasing resources leads to resource leakagedelete
The program exited normally before (for example, ifif
middlereturn
) or abnormal exit, before it candelete
, resulting in resource leakageAt this time, smart pointers are needed. The smartness of smart pointers is mainly reflected in the fact that users do not need to pay attention to the release of resources, because smart pointers will help you completely manage the release of resources. It will ensure that no matter how the program logic runs, it will execute normally or produce exceptions.When resources expire (function scope or program ends), they will be released
Let's implement a simple smart pointer ourselves
template<typename T>
class CSmartPtr
{
public:
CSmartPtr(T* ptr = nullptr) : mptr(ptr) {}
~CSmartPtr() { delete mptr; }
private:
T* mptr;
};
int main()
{
CSmartPtr<int> ptr(new int());
/*其它的代码...*/
/*
由于ptr是栈上的智能指针对象,不管是函数正常执行完,
还是运行过程中出现异常,栈上的对象都会自动调用析构函数,
在析构函数中进行了delete操作,保证释放资源
*/
return 0;
}
Therefore, due to the above characteristics, smart pointers are generally defined on the stack.A smart pointer is a class object, a pointer is passed into the constructor of this class, and the passed pointer is released in the destructor. Since the class object is allocated and released on the stack, it will be automatically released when our function (or program) ends.
So, can we define smart pointers on the heap? For exampleCSmartPtr* p = new CSmartPtr(new int);
, the compilation is successful, but the definition here isp
Although it is a smart pointer type, it is essentially a raw pointer, sop
Still need to manuallydelete
, which brings us back to the problem we faced with raw pointers at the beginning, so don't use it like this
Of course, smart pointers should be similar to raw pointers and provide the common*
and->
The overloaded functions of the two operators are used just like naked pointers. The code is as follows:
template<typename T>
class CSmartPtr
{
public:
CSmartPtr(T* ptr = nullptr) : mptr(ptr) {}
~CSmartPtr() { delete mptr; }
T& operator*() { return *mptr; }
T* operator->() { return mptr; }
private:
T* mptr;
};
int main()
{
CSmartPtr<int> ptr(new int());
*ptr = 20; // operator*()一定要返回引用,这样才可以赋值
cout << *ptr << endl; // 20
class Test
{
public:
void test() { cout << "call Test::test()" << endl; }
};
CSmartPtr<Test> ptr2 = new Test();
(*ptr2).test(); // (*ptr2)取出Test对象,用对象调用方法
// operator->()返回的是一个指针,实现了用指针调用函数
// 即(ptr2.operator->()) -> test()
ptr2->test();
return 0;
}
The smart pointer implemented in the previous section is very similar to an ordinary naked pointer in use, but it still has a big problem. See the following code:
CSmartPtr<int> p1(new int());
CSmartPtr<int> p2(p1); // 拷贝构造
Running the code crashes directly because the default copy constructor does a shallow copy.p1
andp2
Holding the samenew int
resource,p2
First, the destructor releases the resources, thenp1
When destructed, it becomesdelete
Wild pointer, program crashes
So, how to solve the problem of shallow copy? RewriteCSmartPtr
The copy constructor
CSmartPtr(const CSmartPtr<T>& src) { mptr = new T(*src.mptr); }
Now the code runs fine, butp1
andp2
They manage two different resources. If users do not understand, they may mistakenly thinkp1
andp2
Managing the same resource
Therefore, it is wrong to write the copy constructor like this
So, how to solve the shallow copy problem of smart pointers? Two methods:Smart pointer without reference counting, smart pointer with reference counting
In this section, we first look atSmart pointer without reference counting
auto_ptr
(C++98, now deprecated)scoped_ptr
(Boost library)unique_ptr
(C++11, recommended)Include the header file when using:#include <memory>
Let's consider this code first.
auto_ptr<int> ptr1(new int());
auto_ptr<int> ptr2(ptr1);
*ptr2 = 20;
cout << *ptr1 << endl;
Run crash, why? Let's take a lookauto_ptr
Source code
You can see that the copy construction will call the passed objectrelease
method, this method is to_Right
The resource pointed to is returned to the newauto_ptr
object, and_Right
(oldauto_ptr
)of_Myptr
Set tonullptr
In short, the oldauto_ptr
Pointing to new resourcesauto_ptr
Hold, old setnullptr
Therefore, the code above uses*ptr1
It's just wrong.(auto_ptr
Always let the last smart pointer manage resources)
So,auto_ptr
Can it be used in a container? See the following code
int main()
{
vector<auto_ptr<int>> vec;
vec.push_back(auto_ptr<int>(new int(10)));
vec.push_back(auto_ptr<int>(new int(20)));
vec.push_back(auto_ptr<int>(new int(30)));
cout << *vec[0] << endl; // 10
vector<auto_ptr<int>> vec2 = vec;
/* 这里由于上面做了vector容器的拷贝,
相当于容器中的每一个元素都进行了拷贝构造,
原来vec中的智能指针全部为nullptr了,
再次访问就成访问空指针了,程序崩溃
*/
cout << *vec[0] << endl; // 程序崩溃
return 0;
}
Therefore, it is not recommended to use in C++auto_ptr
, unless the application scenario is very simple. Andauto_ptr
Deprecated in C++11 and completely removed in C++17
Summarize:auto_ptr
Smart pointers do not have reference counting, so they handle the shallow copy problem by directly copying the previousauto_ptr
Set tonullptr
, only the last oneauto_ptr
Holding resources
You need to install the Boost library, which includes the header files#include <boost/scoped_ptr.hpp>
Ready to use
take a lookscoped_ptr
Source code:
template<class T> class scoped_ptr
{
private:
scoped_ptr(scoped_ptr const&);
scoped_ptr& operator=(scoped_ptr const&);
...
};
can be seenscoped_ptr
The copy constructor and assignment function are privatized, so the object does not support these two operations and cannot be called, which fundamentally eliminates the occurrence of shallow copies.
soscoped_ptr
It cannot be used in containers. If containers copy or assign values to each other, it will causescoped_ptr
Object copy construction and assignment function, compilation error
scoped_ptr
andauto_ptr
The difference: can be explained by ownership,auto_ptr
The ownership of resources can be transferred arbitrarily, andscoped_ptr
No ownership is transferred (because copy construction and assignment functions are prohibited)
scoped_ptr
Generally not used
Take a look firstunique_ptr
Part of the source code:
template<class _Ty, class _Dx>
class unique_ptr
{
public:
/*提供了右值引用的拷贝构造函数*/
unique_ptr(unique_ptr&& _Right) { ... }
/*提供了右值引用的operator=赋值重载函数*/
unique_ptr& operator=(unique_ptr&& _Right) { ... }
/*
删除了unique_ptr的拷贝构造和赋值函数,
因此不能做unique_ptr智能指针对象的拷贝构造和赋值,
防止浅拷贝的发生
*/
unique_ptr(const unique_ptr&) = delete;
unique_ptr& operator=(const unique_ptr&) = delete;
};
As seen from above,unique_ptr
A little bitscoped_ptr
The same thing is done, that is, copy construction and assignment overload functions are disabled, prohibiting users fromunique_ptr
Perform explicit copy construction and assignment to prevent the occurrence of shallow copy problems of smart pointers.
Since there is no copy constructor,unique_ptr<int> p1(new int()); unique_ptr<int> p2(p1);
Nature is wrong
butunique_ptr
Provides copy construction and assignment functions with rvalue reference parameters, that is,unique_ptr
Smart pointers can be copied and assigned via right-value references, or when generatingunique_ptr
Temporary objects, such asunique_ptr
As the return value of the function, the sample code is as follows:
// 示例1
unique_ptr<int> p1(new int());
unique_ptr<int> p2(move(p1)); // 使用了右值引用的拷贝构造
p2 = move(p1); // 使用了右值引用的operator=赋值重载函数
// 示例2
unique_ptr<int> test_uniqueptr()
{
unique_ptr<int> ptr(new int());
return ptr;
}
int main()
{
/*
此处调用test_uniqueptr函数,在return ptr代码处,
调用右值引用的拷贝构造和赋值函数
*/
unique_ptr<int> p = test_uniqueptr(); // 调用带右值引用的拷贝构造函数
p = test_uniqueptr(); // 调用带右值引用的operator=赋值重载函数
return 0;
}
Then useunique_ptr
The benefit is that users canunique_ptr<int> p2(move(p1));
It can be clearly seen from the statementp1
Transferred resources top2
,p1
No resources are held. If usedauto_ptr
If you do not explicitlymove
When written out, the intention is not obvious, and if you don't understand the underlying structure, you will use it incorrectly.
At the same time, fromunique_ptr
As the name indicates, only one smart pointer can reference a resource. Therefore, it is recommended to give priority to smart pointers without reference counting.unique_ptr
Smart pointers with reference counting mainly includeshared_ptr
andweak_ptr
, with reference countingbenefitThat is, multiple smart pointers can manage the same resource, so what is a smart pointer with reference counting?
With reference counting: Match a reference count to each object's resources
When multiple smart pointers are allowed to point to the same resource, each smart pointer will add 1 to the reference count of the resource. When a smart pointer is destructed, the reference count of the resource will also be reduced by 1. When the last smart pointer reduces the reference count of the resource from 1 to 0, it means that the resource can be released. The destructor of the last smart pointer will handle the release of the resource. This is the concept of reference counting.
Take the previous implementationCSmartPtr
Make modifications and directly upload the code:
// 对资源进行引用计数的类
template<typename T>
class RefCnt
{
public:
RefCnt(T* ptr = nullptr) : mptr(ptr)
{
if (mptr != nullptr)
mcount = 1;
}
void addRef() { mcount++; } // 增加资源的引用计数
int delRef() { return --mcount; }
private:
T* mptr;
int mcount;
};
template<typename T>
class CSmartPtr
{
public:
CSmartPtr(T* ptr = nullptr) : mptr(ptr)
{
// 智能指针构造的时候给资源建立引用计数对象
mpRefCnt = new RefCnt<T>(mptr);
}
~CSmartPtr()
{
if (0 == mpRefCnt->delRef())
{
delete mptr;
mptr = nullptr;
}
}
// 实现拷贝构造
CSmartPtr(const CSmartPtr<T>& src)
:mptr(src.mptr), mpRefCnt(src.mpRefCnt)
{
if (mptr != nullptr)
mpRefCnt->addRef();
}
CSmartPtr<T>& operator=(const CSmartPtr<T>& src)
{
// 防止自赋值
if (this == &src)
return *this;
// 本身指向的资源减1
// 如果减1为0释放资源;如果减1不为0直接走
if (0 == mpRefCnt->delRef()) { delete mptr; }
mptr = src.mptr;
mpRefCnt = src.mpRefCnt;
mpRefCnt->addRef();
return *this;
}
T& operator*() { return *mptr; }
T* operator->() { return mptr; }
private:
T* mptr; // 指向资源的指针
RefCnt<T>* mpRefCnt; // 指向该资源引用计数对象的指针
};
// 那么现在就不会报错了,不会对同一个资源释放多次
CSmartPtr<int> ptr1(new int());
CSmartPtr<int> ptr2(ptr1);
CSmartPtr<int> ptr3;
ptr3 = ptr2;
*ptr1 = 20;
cout << *ptr2 << " " << *ptr3 << endl; // 20 20
This enables multiple smart pointers to manage the same resource
However, the smart pointers we have implemented are not thread-safe and cannot be used in multi-threaded scenarios.shared_ptr
andweak_ptr
It is thread safe!
shared_ptr
:powerfulSmart pointers (can change the reference count of a resource)weak_ptr
:weakSmart pointers (do not change the reference count of the resource)We implemented theCSmartPtr
It is also a strong smart pointer (can change the reference count of resources)
It can be understood like this: Weak smart pointer observe strong smart pointer, strong smart pointerobserve Resources (memory)
So,Cross-reference (circular reference) problem of strong smart pointerWhat is it? Let's take a look
class B; // 前置声明类B
class A
{
public:
A() { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
shared_ptr<B> _ptrb; // 指向B对象的智能指针
};
class B
{
public:
B() { cout << "B()" << endl; }
~B() { cout << "~B()" << endl; }
shared_ptr<A> _ptra; // 指向A对象的智能指针
};
int main()
{
shared_ptr<A> pa(new A()); // pa指向A对象,A的引用计数为1
shared_ptr<B> pb(new B()); // pb指向B对象,B的引用计数为1
pa->_ptrb = pb; // A对象的成员变量_ptrb也指向B对象,B的引用计数为2
pb->_ptra = pa; // B对象的成员变量_ptra也指向A对象,A的引用计数为2
cout << pa.use_count() << endl;
cout << pb.use_count() << endl;
return 0;
}
operation result:
A()
B()
2
2
As you can see, A and B have no destructors, which is why cross-references can causenew
The resources that come out cannot be released, resulting in resource leakage!
analyze:
outmain
Function scope,pa
andpb
The two local objects are destroyed, and the reference counts of objects A and B are reduced from 2 to 1 respectively. The conditions for releasing A and B are not met (the condition for release is that the reference counts of A and B are reduced to 0), so twonew
The A and B objects that come out cannot be released, resulting in memory leaks. This problem isCross-reference (circular reference) problem of strong smart pointer
Solution: Use strong smart pointers when defining objects and weak smart pointers when referencing objects
class B; // 前置声明类B
class A
{
public:
A() { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
weak_ptr<B> _ptrb; // 指向B对象的弱智能指针(引用对象时,用弱智能指针)
};
class B
{
public:
B() { cout << "B()" << endl; }
~B() { cout << "~B()" << endl; }
weak_ptr<A> _ptra; // 指向A对象的弱智能指针(引用对象时,用弱智能指针)
};
int main()
{
// 定义对象时,用强智能指针
shared_ptr<A> pa(new A()); // pa指向A对象,A的引用计数为1
shared_ptr<B> pb(new B()); // pb指向B对象,B的引用计数为1
// A对象的成员变量_ptrb也指向B对象,B的引用计数为1,因为是弱智能指针,引用计数没有改变
pa->_ptrb = pb;
// B对象的成员变量_ptra也指向A对象,A的引用计数为1,因为是弱智能指针,引用计数没有改变
pb->_ptra = pa;
cout << pa.use_count() << endl; // 1
cout << pb.use_count() << endl; // 1
return 0;
}
operation result:
A()
B()
1
1
~B()
~A()
As you can see,main
Function scope,pa
andpb
The two local objects are destroyed, and the reference counts of objects A and B are reduced from 1 to 0, respectively, meeting the conditions for releasing A and B. Thereforenew
The A and B objects that came out were destructed, solving the problemCross-reference (circular reference) problem of strong smart pointer
can be seen,weak_ptr
A weak smart pointer does not change the reference count of a resource, that is, a weak smart pointer only observes whether the object is alive (whether the reference count is 0) and cannot use the resource.
Then if we addvoid testA() { cout << "非常好用的方法!!" << endl; }
Such a method adds in Bvoid func() { _ptra->testA(); }
Is it OK to call this method?
*
and->
Operator overloads cannot use functions similar to naked pointersSolution:
class B; // 前置声明类B
class A
{
public:
A() { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
void testA() { cout << "非常好用的方法!!" << endl; }
weak_ptr<B> _ptrb; // 指向B对象的弱智能指针(引用对象时,用弱智能指针)
};
class B
{
public:
B() { cout << "B()" << endl; }
~B() { cout << "~B()" << endl; }
void func()
{
shared_ptr<A> sp = _ptra.lock(); // 提升方法,出函数作用域就自动析构了
if (sp != nullptr)
sp->testA();
}
weak_ptr<A> _ptra; // 指向A对象的弱智能指针(引用对象时,用弱智能指针)
};
int main()
{
shared_ptr<A> pa(new A());
shared_ptr<B> pb(new B());
pa->_ptrb = pb;
pb->_ptra = pa;
cout << pa.use_count() << endl; // 1
cout << pb.use_count() << endl; // 1
pb->func();
return 0;
}
Although
weak_ptr
does not own the object, but it can be accessed throughlock()
Method attempts to obtain a pointer to an objectshared_ptr
, if the object is still alive (i.e. there are othershared_ptr
point to it),lock()
A pointer to the object will be returnedshared_ptr
Otherwise, an emptyshared_ptr
use
lock()
A typical scenario for a method is when temporary access is requiredweak_ptr
When you point to an object, you do not want to increase the lifetime count of the object.
operation result:
A()
B()
1
1
非常好用的方法!!
~B()
~A()
Now you can see that it was called correctly!
Let’s look at oneThread safety issues when multiple threads access shared objects:Thread A and Thread B access a shared object. If Thread A is destructing the object and Thread B wants to call a member method of the shared object, Thread A may have already destructed the object. If Thread B accesses the object again, an unexpected error will occur.
First look at the following code:
class A
{
public:
A() { cout << "A()" << endl; }
~A() { cout << "~A()" << endl; }
void testA() { cout << "非常好用的方法!!" << endl; }
};
// 子线程
void handler01(A* q)
{
// 睡眠两秒,此时main主线程已经把A对象给delete析构掉了
std::this_thread::sleep_for(std::chrono::seconds(2));
q->testA();
}
// main线程
int main()
{
A* p = new A();
thread t1(handler01, p);
delete p;
//阻塞当前线程,直到调用join()的线程结束
t1.join();
return 0;
}
In executionq->testA();
When this statement is made,main
The thread has already destroyed the shared object, which is obviously unreasonable
If you want to passq
If the pointer wants to access object A, it needs to determine whether object A is alive. If object A is alive, calltestA
There is no problem with the method; if the A object has been destroyed, callingtestA
There is a problem! That is to sayq
When accessing object A, we need to detect whether object A is alive. How to solve this problem? We need to use strong and weak smart pointers.
Let’s look at this code:
// 子线程
void handler01(weak_ptr<A> wp)
{
// 睡眠两秒
std::this_thread::sleep_for(std::chrono::seconds(2));
shared_ptr<A> sp = wp.lock();
if (sp != nullptr)
sp->testA();
else
cout << "A对象已经析构,不能再访问!" << endl;
}
// main线程
int main()
{
shared_ptr<A> p(new A());
thread t1(handler01, weak_ptr<A>(p));
t1.join();
return 0;
}
operation result:
A()
非常好用的方法!!
~A()
You can see it running.sp->testA();
,becausemain
The thread calledt1.join()
The method waits for the child thread to end.wp
passlock
Successfully promoted tosp
Modify the above code:
// 子线程
void handler01(weak_ptr<A> wp)
{
// 睡眠两秒
std::this_thread::sleep_for(std::chrono::seconds(2));
shared_ptr<A> sp = wp.lock();
if (sp != nullptr)
sp->testA();
else
cout << "A对象已经析构,不能再访问!" << endl;
}
// main线程
int main()
{
{
shared_ptr<A> p(new A());
thread t1(handler01, weak_ptr<A>(p));
t1.detach();
}
std::this_thread::sleep_for(std::chrono::seconds(3));
return 0;
}
operation result:
A()
~A()
A对象已经析构,不能再访问!
As you can see, we set a scope and sett1
To separate threads, let smart pointersp
Out of scope to destruct A, then it will printA对象已经析构,不能再访问!
, that iswp
passlock
Failed to upgrade tosp
The above is the thread safety issue of accessing shared objects in multiple threads.shared_ptr
andweak_ptr
A typical application of.
Reference articles:In-depth understanding of C++ smart pointers