extend scope of pass by ref object for std::thread - c++

To demonstrate the problem, let me present a short code -
void someMethod() {
// CustomType obj;
const auto obj = getCustomTypeObj();
std::thread([](customType &obj) {
// some delay
obj.doSomething();
obj.close();
// can now be destructed
}).detach();
// similarly for std::async
std::async(std::launch::async, [](customType &obj){
obj.doSomething();
obj.close();
}
// there might not be any use of obj here
// should not be destructed here because std::thread might not get it.
}
In above code, an CustomType type object is constructed for which copy constructor is deleted. So I must pass it by reference everywhere, or create it from scratch in relevant scope. However for 1 scenario I'm currently dealing with, it is not quite possible to create it in relevant scope which is inside std::thread's execution method.
What I'm afraid of is obj might be destructed before std::thread even completes its job and then I've no idea what's going to happen. So how should I solve this problem of extending it's scope to std::thread's lambda.

Btw your code is incorrect, you do not pass your object, so your code should be instead:
auto obj = getCustomTypeObj();
std::thread([](customType &obj) {
// some delay
obj.doSomething();
obj.close();
// can now be destructed
}, std::ref( obj ) ).detach();
To avoid issue with object lifetime pass your object to the lambda or function by value and move your object there:
auto obj = getCustomTypeObj();
std::thread([](customType arg) { // note by value, not reference
// some delay
arg.doSomething();
arg.close();
// arg will be destroyed here
}, std::move( obj ) ).detach(); // object moved
now lambda or function owns that object and it will be destroyed at the end of the function. Here is the live example, I just used std::unique_ptr there instead of customType as type that has copying disabled to validate that moving works.

Related

Start a thread with a member function should pass a object or pointer or a reference?

I'm confused about starting a thread with a member function. I know I need to pass a class object as the second parameter. But someone passed the object to the thread(), and someone passed an address, and I had tried to pass a reference. Both of them are compiling OK. So I am confused about which one is correct.
class X
{
public:
void do_lengthy_work(){
std::cout << "1:1" << std::endl;
}
};
int main(){
X my_x;
std::thread t(&X::do_lengthy_work, std::ref(my_x)); // pass reference
std::thread t(&X::do_lengthy_work, &my_x); // pass address
std::thread t(&X::do_lengthy_work, my_x); // pass object
t.join();
return 0;
}
The thread constructor begins executing the thread according to the rules of std::invoke. So all 3 of the lines of code you show will do something.
The first two lines (ref and pointer) are fine if you expect the lifetime of the object to be longer than the lifetime of the thread. As you can see from the link to std::invoke above, they are equivalent. Otherwise, the third line copies the object into the thread. This means that the original object now doesn't matter and can be destroyed, but also means that any results will not be visible in the the original object, only the copy.

passing a temporary lambda by const std::function reference should fail but seems to work

This is a simplified version of what I am doing:
#include <iostream>
#include <functional>
class thing
{
public:
void register_fn(const std::function<void()> &fn)
{
m_fn = fn;
}
void run()
{
m_fn();
}
std::function<void()> m_fn;
};
int main() {
// Create a thing object
thing t;
// In a limited scope
{
// Local lambda
auto afn = []{std::cout << "hi\n";};
// Store the lamda by reference
t.register_fn(afn);
}
// Run the stored lambda (which should be destroyed - therefore dangling reference??)
t.run();
// Take a copy
thing t2 = t;
t2.run();
return 0;
}
see it running here: https://godbolt.org/z/6qW3ro
So, I have a class that stores a temporary lambda passed by reference. The lamda afn's scope is limited so that once it is passed to the register function it goes out of scope. Then outside of this scope I call the run function which should be running the (dangling?) reference to the lambda.
This has been working, but recently looking back at my code I have a doubt. Since the lambda is temporary (done here by limiting the scope of my lambda object afn) - this should not work... I can't quite get my head around why it is working - unless by luck and this is undefined behaviour?
Or... what have I mis-understood here? - because that is probably the most likely explanation!
This works principally because you make a copy of the function object. In
void register_fn(const std::function<void()> &fn)
{
m_fn = fn;
}
you assign fn to m_fn which makes a copy and and even though fn is a reference to the local lambda, making a copy means m_fn does not refer to fn, but will get a copy of the function fn has stored in it. This means there is no dangling reference and your code has well defined behavior.
This would be different if the lambda captured a local object by reference, as that capture would become invalid after you leave the scope where the lambda was declared.

Unique ptr move ownership to containing object's method

I'd like to move unique_ptr to its object's method:
class Foo {
void method(std::unique_ptr<Foo>&& self) {
// this method now owns self
}
}
auto foo_p = std::make_unique<Foo>();
foo_p->method(std::move(foo_p));
This compiles, but I don't know if it is not Undefined behavior. Since I moved from the object when also calling a method on it.
Is it UB?
If it is, I could probably fix it with:
auto raw_foo_p = foo_p.get();
raw_foo_p->method(std::move(foo_p))
right?
(Optional Motivation:)
Pass the object around to extend its lifetime. It would live in a lambda until the lambda would be called asynchronously. (boost::asio)
Please, see Server::accept first and then Session::start.
You can see the original implementation used shared_ptr, but I don't see why would that be justified, since I only need one owner of my Session object.
Shared_ptr makes code more complex and it was hard for me to understand, when not familiar with shared_ptr.
#include <iostream>
#include <memory>
#include <utility>
#include <boost/asio.hpp>
using namespace boost::system;
using namespace boost::asio;
using boost::asio::ip::tcp;
class Session /*: public std::enable_shared_from_this<Session>*/ {
public:
Session(tcp::socket socket);
void start(std::unique_ptr<Session>&& self);
private:
tcp::socket socket_;
std::string data_;
};
Session::Session(tcp::socket socket) : socket_(std::move(socket))
{}
void Session::start(std::unique_ptr<Session>&& self)
{
// original code, replaced with unique_ptr
// auto self = shared_from_this();
socket_.async_read_some(buffer(data_), [this/*, self*/, self(std::move(self))] (error_code errorCode, size_t) mutable {
if (!errorCode) {
std::cout << "received: " << data_ << std::endl;
start(std::move(self));
}
// if error code, this object gets automatically deleted as `self` enters end of the block
});
}
class Server {
public:
Server(io_context& context);
private:
tcp::acceptor acceptor_;
void accept();
};
Server::Server(io_context& context) : acceptor_(context, tcp::endpoint(tcp::v4(), 8888))
{
accept();
}
void Server::accept()
{
acceptor_.async_accept([this](error_code errorCode, tcp::socket socket) {
if (!errorCode) {
// original code, replaced with unique_ptr
// std::make_shared<Session>(std::move(socket))->start();
auto session_ptr = std::make_unique<Session>(std::move(socket));
session_ptr->start(std::move(session_ptr));
}
accept();
});
}
int main()
{
boost::asio::io_context context;
Server server(context);
context.run();
return 0;
}
compiles with:
g++ main.cpp -std=c++17 -lpthread -lboost_system
For your first code block:
std::unique_ptr<Foo>&& self is a reference and assigning it an argument std::move(foo_p), where foo_p is a named std::unique_ptr<Foo> will only bind the reference self to foo_p, meaning that self will refer to foo_p in the calling scope.
It does not create any new std::unique_ptr<Foo> to which the ownership of the managed Foo object may be transferred. No move construction or assignment happens and the Foo object is still destroyed with the destruction of foo_p in the calling scope.
Therefore there is no risk of undefined behavior in this function call itself, although you could use the reference self in a way that could cause undefined behavior in the body.
Maybe you intended to have self be a std::unique_ptr<Foo> instead of std::unique_ptr<Foo>&&. In that case self would not be a reference, but an actual object to which ownership of the managed Foo would be transferred via move construction if called with std::move(p_foo) and which would be destroyed after the function call in foo_p->method(std::move(foo_p)) together with the managed Foo.
Whether this alternative variant is in itself potentially undefined behavior depends on the C++ standard version in use.
Before C++17 the compiler was allowed to choose to evaluate the call's arguments (and the associated move construction of the parameter) before evaluating foo_p->method. This would mean, that foo_p could have already moved from when foo_p->method is evaluated, causing undefined behavior. This could be fixed similarly to how you propose to do it.
Since C++17 it is guaranteed that the postfix-expression (here foo_p->method) is evaluated before any of the arguments of the call are and therefore the call itself would not be a problem. (Still the body could cause other issues.)
In detail for the latter case:
foo_p->method is interpreted as (foo_p->operator->())->method, because std::unique_ptr offers this operator->(). (foo_p->operator->()) will resolve to a pointer to the Foo object managed by the std::unique_ptr. The last ->method resolves to a member function method of that object. In C++17 this evaluation happens before any evaluation of the arguments to method and is therefore valid, because no move from foo_p has happened yet.
Then the evaluation order of the arguments is by design unspecified. So probably A) the unique_ptr foo_p could get moved from before this as an argument would be initialized. And B) it will get moved from by the time method runs and uses the initialized this.
But A) is not a problem, since § 8.2.2:4, as expected:
If the function is a non-static member function, the this parameter of the function shall be initialized with a pointer to the object of the call,
(And we know this object was resolved before any argument was evaluated.)
And B) won't matter as: (another question)
the C++11 specification guarantees that transferring ownership of an object from one unique_ptr to another unique_ptr does not change the location of the object itself
For your second block:
self(std::move(self)) creates a lambda capture of type std::unique_ptr<Session> (not a reference) initialized with the reference self, which is referring to session_ptr in the lambda in accept. Via move-construction ownership of the Session object is transferred from session_ptr to the lambda's member.
The lambda is then passed to async_read_some, which will (because the lambda is not passed as non-const lvalue reference) move the lambda into internal storage, so that it can be called asynchronously later. With this move, the ownership of the Session object transfers to the boost::asio internals as well.
async_read_some returns immediately and so all local variables of start and the lambda in accept are destroyed. However the ownership of Session was already transferred and so there is no undefined behavior because of lifetime issues here.
Asynchronously the lambda's copy will be called, which may again call start, in which case the ownership of Session will be transferred to another lambda's member and the lambda with the Session ownership will again be moved to internal boost::asio storage. After the asynchronous call of the lambda, it will be destroyed by boost::asio. However at this point, again, ownership has already transferred.
The Session object is finally destroyed, when if(!errorCode) fails and the lambda with the owning std::unique_ptr<Session> is destroyed by boost::asio after its call.
Therefore I see no problem with this approach with regards to undefined behavior relating to Session's lifetime. If you are using C++17 then it would also be fine to drop the && in the std::unique_ptr<Session>&& self parameter.

When using std::move with a lambda when does the move happen

If I create a lambda in a function and capture a variable to the lambda using std::move, when does the move happen? Is it when the lambda is created or when the lambda is executed?
Take the following code for example ... when do the various moves happen? Is it thread safe if myFunction is called on one thread and testLambda is executed on another thread?
class MyClass {
private:
// Only accessed on thread B
std::vector<int> myStuff;
// Called from thread A with new data
void myFunction(const std::vector<int>&& theirStuff) {
// Stored to be called on thread B
auto testLambda = [this, _theirStuff{ std::move(theirStuff) }]() {
myStuff = std::move(_theirStuff);
};
// ... store lambda
}
// Elsewhere on thread A
void someOtherFunction() {
std::vector<int> newStuff = { 1, 2, .... n };
gGlobalMyClass->myFunction(std::move(newStuff));
}
If I create a lambda in a function and capture a variable to the lambda using std::move, when does the move happen? Is it when the lambda is created or when the lambda is executed?
If you had written what I believe you intended to write, then the answer would be: both. Currently, the answer is: neither. You have a lambda capture _theirStuff { std::move(theirStuff) }. This basically declares a member of the closure type, which will be initialized when the closure object is created as if it were
auto _theirStuff { std::move(theirStuff) };
You also have
myStuff = std::move(_theirStuff);
in the lambda body.
However, your parameter theirStuff is actually an rvalue reference to a const std::vector<int>. Thus, _theirStuff { std::move(theirStuff) } is not actually going to perform a move, because a const std::vector cannot be moved from. Most likely, you wanted to write std::vector<int>&& theirStuff instead. Furthermore, as pointed out by #JVApen in the comments below, your lambda is not mutable. Therefore, _theirStuff will actually be const as well, and, thus, also cannot be moved from. Consequently, your code above, despite all the std::move, will actually make a copy of the vector every time. If you had written
void myFunction(std::vector<int>&& theirStuff)
{
auto testLambda = [this, _theirStuff { std::move(theirStuff) }]() {
myStuff = std::move(_theirStuff);
};
}
You would be moving theirStuff into _theirStuff when the closure object is created. And you would be copying _theirStuff into myStuff when the lambda is called. If you had written
void myFunction(std::vector<int>&& theirStuff)
{
auto testLambda = [this, _theirStuff { std::move(theirStuff) }]() mutable {
myStuff = std::move(_theirStuff);
};
}
Then you would be moving theirStuff into _theirStuff when the closure object is created. And you would be moving _theirStuff into myStuff when the lambda is called. Note that, as a consequence, your lambda then cannot really be called twice. I mean, it can, but it will only really work once since _theirStuff will be empty after the first time the lambda is called…
Also, note that above description is only valid for the particular combination of types in your example. There is no general definition of what it actually means to move an object. What it means to move an object is entirely up to the particular type of the object. It may not even mean anything. std::move itself does not really do anything. All it does is cast the given expression to an rvalue reference. If you then initialize another object from the result of std::move, or assign the result to an object, overload resolution will pick a move constructor or move assignment operator—if one exists—instead of the normal copy constructor or copy assignment operator. It is then up to the implementation of the move constructor/move assignment operator of the respective type to actually perform a move, i.e., do whatever it is that's supposed to be done for the particular type in case of initialization or assignment from an rvalue. So, in a way, what you do when you apply std::move is that you advertise the respective object as "this may be moved from". Whether or not it actually will be moved from (and, if so, what that actually means) is up to the implementation. In the particular case of std::vector, the move constructor/move assignment operator, by definition, guarantee that not only the contents of the original vector will be taken over from the original object, but also that the original object will be empty afterwards. In many other cases, it may be undefined behavior to do anything with an object that was moved from (except, maybe, destroy it; that one can be pretty much taken for granted as a type that doesn't at least allow that would be pretty much useless; typically, you will at least be able to assign a new value to an object that was moved from, but even that is not guaranteed in general). You always have to check for the particular type at hand what condition an object is guaranteed to be in after having been moved from…

Passing a mutable lambda with unique_ptr into a const& std::function

I have got a dispatch function which executes a given lambda in a main thread. For the sake of this question, suppose it looks like the following:
void dispatch(const std::function<void()>& fn) {
fn();
}
I need to load a new object in a new thread without interrupting the main thread. So I do the following: 1) start a new thread and create a new unique pointer inside the thread, 2) call dispatch and propagate the new unique pointer where it belongs.
std::unique_ptr<std::string> foo; // nullptr
// do the loading in a new thread:
std::thread t([&](){
// in the new thread, load new value "Blah" and store it temporarily
auto bar = std::make_unique<std::string>("Blah");
dispatch([bar2 = std::move(bar), &foo]() mutable {
foo = std::move(bar2); // propagate the loaded value to foo
});
});
t.join(); // for the sake of this example
std::cout << "foo = " << *foo << std::endl; // this should say: foo = Blah
Run example online: http://cpp.sh/5zjvm
This code does not compile because the inner lambda in dispatch is mutable and so does not fit into dispatch(const std::function<void()>& fn) which requires a const&.
The lambda, however, needs to be mutable because it needs to call std::move on the unique pointers.
This code could be fixed for example by changing dispatch to:
template <typename Fn>
void dispatch(Fn fn) {
fn();
}
Unfortunately, the dispatch function is an API of a library and I cannot change it.
Is there a way out of this problem without getting rid of unique pointers?
No, that isn't your problem.
Your problem is that your lambda cannot be copied, as it has a unique ptr captured by value in it.
std::function<Sig> type erases down to
Invoke with Sig
Destroy
Copy (and sometimes move)
Cast-back-to-original-type
Your lambda cannot be copied, so cannot be stored in a std::function.
The lazy-coder's solution is:
dispatch([bar2 = std::make_shared<decltype(bar)>(std::move(bar)), &foo]() mutable {
foo = std::move(*bar2);
});
where we shove the non-copyable state into a shared_ptr.