Some notes about C++ lamba capture, shared_ptr/weak_ptr, they are not quite obvious!
(1) lambda capture implement
it is simply just put the captures as members either by copy or reference.
special capture this point: we don’t need to make a distinction between local variables and fields of a class when writing lambda functions.
how to ensure this will be valid? if we pass lamda around?
(2) lambda + shared_ptr => memory leak.
if we use lambda with shared_ptr capture, in some scenarios the shared_ptr will never be released due to implicit circular reference.
for example ( bad example)
class Encoder {
public:
typedef std::function<void()> rtp_send ;
Encoder(): t( std::thread( while(run_){ …; rtp_send sender_(pkt); } ) );
~Encoder() { run_= false; t.join() }
void set_rtp_send(rtp_send r_send) { sender_ = r_send; }
private: atomic<boo> run_ = true;rtp_send sender_; std::thread t;
}
class Call {
public:
specific_rtp_port_send() { rtp_channal->send(); };
shared_ptr<Encoder> e;
rtp_channal = make_shared<rtp_channel>();
}
/// code in main
main() {
encoder = make_shared<Encoder>(); // hold share_ptr<encoder> which include shared_ptr<call> in encoder’s lamba capture
auto c = make_shared<Call>();
c->e = encoder; // each call has a shared encoder may shared with other calls for efficiency reason.
encoder->set_rtp_send( [c]() { c->specific_rtp_port_send(); });
}
as you can see the Call hold reference to e, and e implicitly hold reference to call, so the resource is never released.
Thus here we can use weak_ptr instead of shared_ptr as wc.lock() will atomically give us a valid shared_ptr or nullptr.
encoder->set_rtp_send( [wc =std::weak_ptr<Call>{c} ]() { if( auto c = wc.lock() ;c ) { c->specific_rtp_port_send(); } });
(3) shared_ptr could be released at any thread/process ( even on its own thread who cause it out of scope):
as long as share_ptr is out of scope, the system decrease the count, if reach 0, it will call the destructor.
so it could be release at any thread/process.
but if it called from its own thread, thing become troublesome.
As showed in the above example, depending on time,
if call pop encoder, and rtp_send thread not running, then call will get destructed in main thread, we are good.
but if rtp_send thread wc.lock() succeed, the rtp_thread will hold a valid shared_ptr<call> until if finished, then call get destructed. which cause the call’s destructed to be called.
Then Encoder get destructed in the rtp_thread, which will invoke t.join() the the same thread, that means it calling itself to stop, it will crash!!
we can lock something else e.g: rtp_channel instead of call to
encoder->set_rtp_send( [wc =std::weak_ptr<rtp_channel>{c} ]() { if( auto chan = wc.lock() ;chan ) { chan->send(); } });
But it is so tricky. we should re-struct/re-design the whole thing, we probably do not need encoder inside a call.
The whole point is: shared_ptr could be release at any thread/place as long as its ref count is 0.
we need to be very careful if we use lamba capture even with weak_ptr.
https://www.geeksforgeeks.org/weak_ptr-in-cpp/#
The weak_ptr is one of the smart pointers that provide the capability of a pointer with some reduced risks as compared to the raw pointer. The weak_ptr, just like shared_ptr has the capability to point to the resource owned by another shared_ptr but without owning it. In other words, they are able to create a non-owning reference to the object managed by shared_ptr.
Need of weak_ptr
To understand the need for weak_ptr, we need to first understand the use case of shared_ptr which leads to a common problem called a circular link. It occurs when two or more objects reference each other using a shared_ptr. For example, if ObjectA has a shared_ptr to ObjectB and ObjectB has a shared_ptr to ObjectA, they form a circular reference. This can be problematic because neither ObjectA nor ObjectB will ever be deleted, leading to a memory leak.
Here, weak_ptr comes to the rescue by providing a way to break these circular references. It allows you to create a non-own reference to an object managed by shared_ptr without affecting the reference count or preventing the object from being deleted.
Syntax of weak_ptr
The weak_ptr can be declared using the following syntax:
std::weak_ptr<type> name;
where type is the type of data it is pointing to.
std::weak_ptr Member Functions
The following are some member functions associated with std::weak_ptr to provide different functionalities.
S.No. | Functions | Description |
---|---|---|
1 | reset() | Clear the weak_ptr. |
2 | swap | Specialization of std:swap(). It swaps the objects managed by weak_ptr. |
3 | expired() | Check if the resource weak_ptr pointing to exists or not. |
4 | lock() | If the resource pointed by weak_ptr exists, this function returns a shared_ptr with ownership of that resource. If the resource does not exist, it returns default constructed shared_ptr. |
5 | use_count() | Tells about how many shared_ptr owns the resource. |
Example of weak_ptr
The following example demonstrates how the weak_ptr solves the circular reference problem.
- C++
// C++ program to illustrate the use of weak_ptr #include <iostream> #include <memory> using namespace std; // declaring a dummy object class Object { public : Object( int value) : data(value) { cout << "Object created with value: " << data << endl; } ~Object() { cout << "Object destroyed with value: " << data << endl; } int data; }; // driver code int main() { // creating shared pointer with resource ownership shared_ptr<Object> sharedObjectA = make_shared<Object>(42); // creating weak pointer to the previously created // shared objects weak_ptr<Object> weakObjectA = sharedObjectA; // Access objects using weak_ptr if (!weakObjectA.expired()) { cout << "The value stored in sharedObjectA:" << (*weakObjectA.lock()).data << endl; } // deleting object sharedObjectA.reset(); cout << "End of the Program" ; return 0; } |
Object created with value: 42 The value stored in sharedObjectA:42 Object destroyed with value: 42 End of the Program
In this example, we create a shared_ptr with the ownership of a dynamically created object. Then we created a weak_ptr which refers to the same resource as that of shared_ptr. After resetting the shared_ptr, we can see that its is destroyed and deallocated from the memory.
Applications of weak_ptr
The following are the primary applications of the weak_ptr:
- Preventing Circular References: The primary application of weak_ptr is to prevent circular references. When an object wants to reference another object without owning it, it can use weak_ptr. This ensures that no circular references are created and objects can be safely freed when they are no longer needed.
- Cache Systems: weak_ptr is commonly used in cache implementations. Caches often need to temporarily store references to objects without preventing the deletion of those objects when they are no longer in use. weak_ptr provides an elegant solution for this use case