c++ lambda capture, weak_ptr, shared_ptr many subtle caveats

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.

 

 

Please rate this


Leave a Reply

Your email address will not be published. Required fields are marked *


*

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>