Thus, the common ptr and weak ptr are thread safe, if you have an instance of an object local to the given stream, and they have a common object with a pointed object, you can interact with it in one stream, and the other is all works.
For this to work correctly, you must use them correctly.
wp.expired() is only useful to do things like "delete every expired weak ptr from the buffer". This is not useful for your purpose.
Every weak pointer that has expired has expired. But the weak pointer involved can become inactive after you check it.
if(!wpPtr.expired()) {
In <<--- here we do not know anything about the state of wpPtr in a multi-threaded environment. It may be expired or not expired. On the other hand:
if(wpPtr.expired()) {
In <<--- there we know that the weak pointer has expired.
As with the io file and other types of transactional operations, the only way to check if you can do something is to try it. Between the definition, you should be able to do it and do it, the state may change, and the operation may fail.
Sometimes you can decide that you almost certainly could not do it before, which is sometimes useful, but you cannot be sure that you can do it until you try. An attempted attempt may fail, after which you process the error.
if(auto spFoo = wpPtr.lock()) { spFoo->DoSomething(); }
this is the “right” way to interact with a weak pointer. Check the validity of the weak pointer and get a generic pointer in the same operation.
Creating spFoo outside the if() header is acceptable, I prefer this method, since the scope of spFoo limited to the exact scope where it is valid.
Another preferred method is an early exit where you wrote your code as SFINAE friendly:
auto spFoo = wpPtr.lock(); if(!spFoo) return error("wp empty"); spFoo->DoSomething();
which makes the "expected" execution of the code stream in a flat line without indentation or conditions or transitions.