I have to use smart pointers in my work recently. I have studied it again. I searched for a lot of information online and added the content I reprinted before. I sorted it out myself to take the essence. There are some examples I think of, and I mainly prefer to use it.
There are also some text descriptions cited from other articles (the descriptions are clear), but there are too many sources that are difficult to find, so I am sorry.
During the process of running the program, we often experience segfaults and continuous memory increase, which are problems in C++ explicit memory management. They are mainly summarized into the following points:
1. Wild pointer: Some memory units have been released, but the previous pointer to it is still in use.
2. Repeat release: The program tries to free the memory unit that has been released.
3. Memory Leak: No memory unit that is no longer used is released.
4. Buffer overflow: array crosses boundary.
5. Unpaired new[]/delete
In response to the above 1 to 3 problems, the C++ standard provides smart pointers to solve them.
Smart pointers are classes (templates) implemented based on the RAII (Resource Acquisition Is Initialization) mechanism, and have the behavior of pointers (overloading operator* and operator-> operators). When an object is created, it is initialized; after leaving its scope, the resource is released by automatically calling the destructor.
In C++98, smart pointers are implemented through a template type "auto_ptr". The auto_ptr object points to the dynamic memory created by new by initializing. When the life cycle of the auto_ptr object ends, its destructor will automatically release the dynamic memory owned by the auto_ptr object. Even if an exception occurs, dynamic memory can be released through the exception stack expansion process. But it has some disadvantages:
- The target object for assignment and copy operations will first release the object it originally owns.
{ auto_ptr<int> ap1(new int(100)); auto_ptr<int> ap2(ap1); // ap1 == nullptr; }
auto_ptr cannot be used in stl containers, because the type required by stl containers must beValue semantic type. That is, two objects are equal after copying or assignment (ap1 == ap2)
- Delete is called when auto_ptr destructuring, so you cannot use auto_ptr to manage array pointers.
Therefore, in the C++11 standard, smart pointers such as unique_ptr, shared_ptr and weak_ptr are used instead to manage dynamic memory. auto_ptr is left behind for compatibility with previous code and is not recommended.~auto_ptr() { delete _M_ptr; }
Header file
When using these three smart pointers (unique_ptr, shared_ptr and weak_ptr), they need to include header files: <memory>
The namespace is: std
unique_ptr
concept:
- unique_ptr "unique" has the object it refers to, and at the same time there can only be one unique_ptr pointing to the given object (std::move() is implemented by prohibiting copy semantics and only moving semantics).
- The life cycle of the unique_ptr pointer itself: starts from when the unique_ptr pointer is created until it leaves the scope. When leaving scope, if it points to an object, the object it refers to is destroyed (the delete operator is used by default, and the user can specify other operations).
- The relationship between unique_ptr pointer and the object it points to: During the life cycle of the smart pointer, the object pointed to by the smart pointer can be changed, such as when creating a smart pointer, it is specified by the constructor, it is re-specified by the reset method, it is released through the release method, and it is transferred through the moving semantics.
Basic usage:
#include <iostream> #include <memory> #include <vector> using namespace std; struct Foo { Foo() {} ~Foo() {} void Print() { cout << "Foo" << endl; } }; int main(void) { Foo* p1 = new Foo(); unique_ptr<Foo> up1; // up1==nullptr // up1 = p1; // Compile error, such assignment is not supported (p1); // Replace the management object and release the previously managed object p1 = nullptr; // unique_ptr<Foo> up2(up1); // Compile error, this construction is not supported unique_ptr<Foo> up2(std::move(up1)); // Up1 ownership is transferred to up2. up1==nullptr (up2); // Exchange pointers of up2 and up1 management objects. up2==nullptr if (up1) { // up1 != nullptr up1->Print(); // unique_ptr is overloaded-> (*up1).Print(); // unique_ptr overloaded* } // up2->Print(); // Error up2 == nullptr, you must first judge before calling p1 = (); // get() returns the pointer of the managed object, up1 continues to hold its management rights p1 = (); // release() returns the pointer to the management object and releases the management rights, up1==nullptr delete p1; unique_ptr<Foo> up3(new Foo()); (); // Display the memory of the release management object, you can also do this: up = nullptr; vector<unique_ptr<Foo>> v; unique_ptr<Foo> up4(new Foo()); // v.push_back(up4); // Compile error, such copying is not supported v.push_back(std::move(up4); // Up4 can only give up ownership of it and transfer ownership to the container through std::move() return 0; }
Application scenarios:
- As long as the unique_ptr smart pointer is created successfully, its destruction will be called to ensure the release of dynamic resources - avoid memory leakage.
- Pass unique_ptr as a reference parameter to other routines, and don't worry that the pointer will be copied or accidentally released in the routine.
shared_ptr
concept:
- shared_ptr is implemented based on the "reference counting" model. Multiple shared_ptr objects can have the same dynamic object and maintain a shared reference count. When the last shared_ptr pointing to the object is destroyed or reset, the object it refers to will be automatically released and dynamic resources will be recycled.
- When destroying this object, use the default delete/delete[] expression, or a custom deleter passed in when constructing shared_ptr to achieve personalized resource release actions.
Basic usage
#include <iostream> #include <memory> #include <vector> #include <> using namespace std; struct Foo { int v; }; int main(void) { shared_ptr<Foo> sp1(new Foo{10}); cout << () << endl; // 1 When currently shared_ptr only has Foo management rights, returns true, otherwise returns false cout << sp1.use_count() << endl; // 1 Returns the reference count of the current object shared_ptr<Foo> sp2(sp1); assert(sp1->v == sp2->v); // sp1 and sp2 jointly own Foo object cout << () << endl; // 0 false cout << sp2.use_count() << endl; // 2 (); // Release the management rights of Foo, and reduce the reference count by 1 assert(sp1 == nullptr); // sp1 is empty cout << () << endl; // 0 will not throw an exception cout << sp1.use_count() << endl; // 0 will not throw an exception cout << () << endl; // 0 No exception runs // cout << sp1->v << endl; // When executing error sp1 is nullptr, both operator* and operator-> will cause undefined behavior cout << () << endl; // 1 true (sp2); // Switch management rights between sp1 and sp2 and reference count assert(sp2 == nullptr); cout << (*sp1).v << endl; // 10 Same as sp1->v vector<shared_ptr<Foo>> vec; vec.push_back(sp1); cout << sp1.use_count() << endl; // 2 return 0; // The vector first destructs the object reference count stored in it, and the object reference count is reduced by 1, but not 0, and the object is not released // Destruction after sp1, the reference count is reduced by 1, and becomes 0, freeing the memory resource of the referred object }
Main methods
T& operator*() const; T* operator->() const; T* get() const; bool unique() const; long use_count() const; void swap(shared_ptr<T>& b);
Application scenarios:
- In a map (unordered_map), multiple keys index a value, using shared_ptr.
#include <iostream> #include <memory> #include <map> #include <cstdint> using namespace std; Class SessionNode { public: SessionNode() {} virtual ~SessionNode() {} }; typedef std::shared_ptr<SessionNode> SessionNodeSP; int main(void) { map<uint64_t, SessionNodeSP> map; uint64_t imsi = 4600000; uint32_t tmsi = 0x12345; { SessionNodeSP sp(new SessionNode()); map[imsi] = sp; map[tmsi] = sp; cout << sp.use_count() << endl; // 3 } // sp Destroy, the reference count is reduced by 1, and becomes 2 (tmsi); // use_count() is 1 (imsi); // use_count() is 0, freeing the memory of managed objects return 0; }
- Multiple map indexes the same value
#include <iostream> #include <memory> #include <map> #include <cstdint> using namespace std; struct Node { uint64_t imsi; uint32_t tmsi; }; typedef std::shared_ptr<Node> NodeSP; class Session { public: Session() {} ~Session() {} NodeSP GetNode(uint64_t imsi, uint32_t tmsi) { NodeSP sp(new Node{imsi, tmsi}); imsi_map_[imsi] = sp; tmsi_map_[tmsi] = sp; return sp; } void EraseNode(NodeSP& sp) { if (sp == nullptr) return; imsi_map_.erase(sp->imsi); imsi_map_.erase(sp->tmsi); } private: map<uint64_t, NodeSP> imsi_map_; map<uint32_t, NodeSP> tmsi_map_; }; int main(void) { Session ses; uint64_t imsi = 4600000; uint32_t tmsi = 0x12345; NodeSP sp; sp = (imsi, tmsi); cout << sp.use_count() << endl; // 3 // ... do something with sp (sp); cout << sp.use_count() << endl; // 1 (); // Actively release the memory of managed objects return 0; }
- Prevent naked pointers from being deleted
#include <iostream> #include <memory> class Cdr { public: Cdr() {} protected: virtual ~Cdr() {} }; class SignalCdr : public Cdr { public: SignalCdr() {} virtual ~SignalCdr() {} }; typedef std::shared_ptr<Cdr> CdrSP; CdrSP CreateSignalCdr() { CdrSP sp(new SignalCdr()); return sp; }; int main(void) { CdrSP sp_cdr = CreateSignalCdr(); SignalCdr* p_signal_cdr = reinterpret_cast<SignalCdr*>(sp_cdr.get()); // ... do something // delete p_signal_cdr; // Execution error, ~Cdr() is protected return 0; }
- Use weak_ptr when causing a circular reference.
#include <iostream> #include <memory> using namespace std; struct Husband; struct Wife; typedef std::shared_ptr<Husband> HusbandSP; typedef std::shared_ptr<Wife> WifeSP; struct Wife { ~Wife() { cout<< "wife distroy" << endl; } HusbandSP sp_hb; }; struct Husband { ~Husband() { cout<< "husband distroy" << endl; } WifeSP sp_wf; }; int main(void) { { HusbandSP husband(new Husband()); WifeSP wife(new Wife()); husband->sp_wf = wife; wife->sp_hb = husband; } // husband and wife refer to each other, and the reference count is 1 when leaving the scope, causing memory leakage return 0; }
weak_ptr
concept
- weak_ptr is a smart pointer introduced in conjunction with shared_ptr. It can only be constructed through shared_ptr or weak_ptr.
- Weak_ptr is the "observer" of shared_ptr and does not modify the reference count of the objects managed by shared_ptr. When shared_ptr is destroyed, weak_ptr will be set to empty. Therefore, the advantage of using weak_ptr over the underlying pointer is that it can know whether the pointed object is valid or not.
- weak_ptr does not have the behavior of a normal pointer, because there is no overloading of operator* and ->. Therefore, when the object observed by weak_ptr exists and needs to be modified, it needs to be promoted to shared_ptr to operate.
Basic usage
#include <iostream> #include <memory> using namespace std; struct Cdr{ int v; }; typedef std::shared_ptr<Cdr> CdrSP; typedef std::weak_ptr<Cdr> CdrWP; void UpdateCdr(CdrWP wp) { if (() == false) { // Check whether the managed object is deleted, true deleted, false has not been deleted; faster than use_count()==1 CdrSP sp = (); // Promote to strong citation if (sp != nullptr) { // If the promotion fails, shared_ptr is nullptr, this example will not fail sp->v *= 2; cout << sp.use_count() << endl; // The reference count is 2 at this time } } // sp delete, reference count minus 1 (); // Show that the ownership is released, or the scope will be automatically released } int main(void) { CdrSP sp(new Cdr{10}); UpdateCdr(sp); // Operation on sp cout << sp->v << endl; // 20 return 0; }
Main methods
long use_count() const; bool expired() const; std::shared_ptr<T> lock() const;
Application scenarios
- For example, the basic usage is to observe the shared_ptr managed objects through weak_ptr. If it is effective, it is promoted to shared_ptr for operation.
- Storage of weak references in map
#include <iostream> #include <memory> #include <map> using namespace std; struct Cdr { int v; }; typedef std::shared_ptr<Cdr> CdrSP; typedef std::weak_ptr<Cdr> CdrWP; class Cache { public: Cache() {} ~Cache() {} void UpdateIndex(CdrSP& sp) { if (sp != nullptr && sp->v > 0) cdr_map_[sp->v] = sp; } private: map<int, CdrWP> cdr_map_; }; int main(void) { Cache cache; CdrSP sp_cdr(new Cdr{1}); (sp_cdr); cout << sp_cdr.use_count() << endl; // 1 return 0; // sp_cdr is destroyed, the reference count is 1, and the memory of the management object is released // cache is destroyed, because the stored in the map is weak_ptr and weap_ptr is empty, so no secondary release will be generated }
bad_weak_ptr exception capture
When shared_ptr is constructed through the weak_ptr parameter, and weak_ptr points to an object that has been deleted, a std::bad_weak_ptr exception is thrown.
#include <iostream> #include <memory> using namespace std; int main() { shared_ptr<int> sp1(new int(1)); weak_ptr<int> wp(sp1); (); try { shared_ptr<int> sp2(wp); } catch (const std::bad_weak_ptr& e) { cout << () << endl; // "std::bad_weak_ptr" } }
Create shared_ptr from this
Sometimes, you need to get shared_ptr from this, that is, you want your class to be managed by shared_ptr, and you need to convert "self" into shared_ptr method.
#include <iostream> #include <memroy> using namespace std; class Foo; typedef std::shared_ptr<Foo> FooSP; void DoSomething(const FooSP& sp) { cout << sp.use_count() << endl; // 2 } class Foo : public std::enable_shared_from_this<Foo> { public: Foo() {} ~Foo() {} void Do() { DoSomething(shared_from_this()); } }; int main(void) { FooSP sp(new Foo()); cout << sp.use_count() << endl; // 1 sp->Do(); return 0; }
Summarize:
- In unique_ptr/shared_ptr, get() exposes the underlying pointer to be compatible with old programs and is generally not recommended because it is difficult to ensure that other routines will do something with this pointer, such as delete/delete[].
- When passed as shared_ptr and weak_pt as parameters, reference pass is used to reduce overhead.
- weka_ptr does not overload operator==, so it cannot be compared. shared_ptr can be compared, and the underlying pointer is compared.
- auto_ptr, unique_ptr, shared_ptr, cannot be used like this
When leaving scope, both ap1 and ap2 try to delete p, causing double free.{ int* p = new int(100); auto_ptr<int> ap1(p); auto_ptr<int> ap2(p); }
- Using smart pointers does not require manual processing of reference counts and calling delete to free resources. But be clear when resources will be released.
For example: use a container to store shared_ptr and release resources when deleted from the container, then other routines only update their resources when using shared_ptr;
If the resource is not released when deleting shared_ptr from the container, it should be shared by another shared_ptr; at this time, it is more appropriate to store weak_ptr in the container.
English website:
/w/cpp/memory
Chinese website (google machine translation):
/w/cpp/memory