Is it possible QMetaObject::invokeMethod with non-copyable argument?
-
I know you can't use signals and slots with non-copyable arguments. One signal can be connected to multiple slots, so it must be possible to copy arguments to every slot. But invokeMethod? I can't see any reason for it to not work. Yet I can't figure it out. Example:
Simple class inheritingQObject:class Bar : public QObject { Q_OBJECT public: void foo1() { qDebug() << "foo1"; } void foo2(int i) { qDebug() << "foo2 i" << i;} void foo3(std::unique_ptr<int> i) {qDebug() << "foo3 s.i:" << *(i.get()); } };Let's create an instance of this class and move it to another thread:
Bar * bar = new Bar(); QThread barThread; bar->moveToThread(&barThread); barThread.start();Now, I want to call
foo1,foo2andfoo3in this new thread. UsingQMetaObject::invokeMethodit's easy to callfoo1andfoo2:QMetaObject::invokeMethod(bar, &Bar::foo1); int i2 = 7; QMetaObject::invokeMethod(bar, [bar, i2](){bar->foo2(i2);});But when I try to call
foo3, the program doesn't compile:auto i3 = std::make_unique<int>(3); QMetaObject::invokeMethod(bar, [bar, i3](){bar->foo3(i3);});It's because
unique_ptris not copyable. So I usestd::move:QMetaObject::invokeMethod(bar, [c, i = std::move(i3)](){bar->foo3(std::move(i));});Still doesn't compile. Am I doing something wrong or is it just not possible to use
QMetaObject::invokeMethodwith non-copyable argument? -
I know you can't use signals and slots with non-copyable arguments. One signal can be connected to multiple slots, so it must be possible to copy arguments to every slot. But invokeMethod? I can't see any reason for it to not work. Yet I can't figure it out. Example:
Simple class inheritingQObject:class Bar : public QObject { Q_OBJECT public: void foo1() { qDebug() << "foo1"; } void foo2(int i) { qDebug() << "foo2 i" << i;} void foo3(std::unique_ptr<int> i) {qDebug() << "foo3 s.i:" << *(i.get()); } };Let's create an instance of this class and move it to another thread:
Bar * bar = new Bar(); QThread barThread; bar->moveToThread(&barThread); barThread.start();Now, I want to call
foo1,foo2andfoo3in this new thread. UsingQMetaObject::invokeMethodit's easy to callfoo1andfoo2:QMetaObject::invokeMethod(bar, &Bar::foo1); int i2 = 7; QMetaObject::invokeMethod(bar, [bar, i2](){bar->foo2(i2);});But when I try to call
foo3, the program doesn't compile:auto i3 = std::make_unique<int>(3); QMetaObject::invokeMethod(bar, [bar, i3](){bar->foo3(i3);});It's because
unique_ptris not copyable. So I usestd::move:QMetaObject::invokeMethod(bar, [c, i = std::move(i3)](){bar->foo3(std::move(i));});Still doesn't compile. Am I doing something wrong or is it just not possible to use
QMetaObject::invokeMethodwith non-copyable argument?@Tarae Your example does not show a problem with
invokeMethodbut with your usage of lambda. Even withoutinvokeMethodthis won't work, because you're trying to capturei3by copy and unique_ptr is not copyable:auto i3 = std::make_unique<int>(3); auto lambda = [bar, i3](){bar->foo3(i3);} // nope, can't doYou need to move the parameter into a capture variable:
auto lambda = [bar, i3cp = std::move(i3)](){bar->foo3(i3cp);}but this won't compile either, because your function takes a parameter by value, so there's another copy there. You have to move your parameter again:
auto i3 = std::make_unique<int>(3); auto lambda = [bar, i3cp = std::move(i3)](){bar->foo3(std::move(i3cp));}but then you have another problem, because lambdas are constant by default i.e. its captures are const too and not movable, so you have to make your lambda mutable:
auto lambda = [bar, i3cp = std::move(i3)]() mutable { bar->foo3(std::move(i3cp)); }This will work. Notice, that this lambda is non-copyable, because it captures a unique_ptr, which itself is non-copyable, yet you can still use that lambda in
invokeMethod. That's becauseinvokeMethodwill move it if it needs to (e.g. when scheduled on another thread's queue).But all in all moving unique_ptrs around like this is kinda shady. It's hard to keep track of what's where and who holds it. I gave you a solution, but I would advise against using it.
-
I know you can't use signals and slots with non-copyable arguments. One signal can be connected to multiple slots, so it must be possible to copy arguments to every slot. But invokeMethod? I can't see any reason for it to not work. Yet I can't figure it out. Example:
Simple class inheritingQObject:class Bar : public QObject { Q_OBJECT public: void foo1() { qDebug() << "foo1"; } void foo2(int i) { qDebug() << "foo2 i" << i;} void foo3(std::unique_ptr<int> i) {qDebug() << "foo3 s.i:" << *(i.get()); } };Let's create an instance of this class and move it to another thread:
Bar * bar = new Bar(); QThread barThread; bar->moveToThread(&barThread); barThread.start();Now, I want to call
foo1,foo2andfoo3in this new thread. UsingQMetaObject::invokeMethodit's easy to callfoo1andfoo2:QMetaObject::invokeMethod(bar, &Bar::foo1); int i2 = 7; QMetaObject::invokeMethod(bar, [bar, i2](){bar->foo2(i2);});But when I try to call
foo3, the program doesn't compile:auto i3 = std::make_unique<int>(3); QMetaObject::invokeMethod(bar, [bar, i3](){bar->foo3(i3);});It's because
unique_ptris not copyable. So I usestd::move:QMetaObject::invokeMethod(bar, [c, i = std::move(i3)](){bar->foo3(std::move(i));});Still doesn't compile. Am I doing something wrong or is it just not possible to use
QMetaObject::invokeMethodwith non-copyable argument?Hi,
AFAIK, I don't think you can move around unique_ptr with invokeMethod.
See @Chris-Kawa's excellent answer.That noted, all your examples with unique_ptr looks like you are trying to reproduce with a lambda what invokeMethod already does.
-
I know you can't use signals and slots with non-copyable arguments. One signal can be connected to multiple slots, so it must be possible to copy arguments to every slot. But invokeMethod? I can't see any reason for it to not work. Yet I can't figure it out. Example:
Simple class inheritingQObject:class Bar : public QObject { Q_OBJECT public: void foo1() { qDebug() << "foo1"; } void foo2(int i) { qDebug() << "foo2 i" << i;} void foo3(std::unique_ptr<int> i) {qDebug() << "foo3 s.i:" << *(i.get()); } };Let's create an instance of this class and move it to another thread:
Bar * bar = new Bar(); QThread barThread; bar->moveToThread(&barThread); barThread.start();Now, I want to call
foo1,foo2andfoo3in this new thread. UsingQMetaObject::invokeMethodit's easy to callfoo1andfoo2:QMetaObject::invokeMethod(bar, &Bar::foo1); int i2 = 7; QMetaObject::invokeMethod(bar, [bar, i2](){bar->foo2(i2);});But when I try to call
foo3, the program doesn't compile:auto i3 = std::make_unique<int>(3); QMetaObject::invokeMethod(bar, [bar, i3](){bar->foo3(i3);});It's because
unique_ptris not copyable. So I usestd::move:QMetaObject::invokeMethod(bar, [c, i = std::move(i3)](){bar->foo3(std::move(i));});Still doesn't compile. Am I doing something wrong or is it just not possible to use
QMetaObject::invokeMethodwith non-copyable argument?@Tarae Your example does not show a problem with
invokeMethodbut with your usage of lambda. Even withoutinvokeMethodthis won't work, because you're trying to capturei3by copy and unique_ptr is not copyable:auto i3 = std::make_unique<int>(3); auto lambda = [bar, i3](){bar->foo3(i3);} // nope, can't doYou need to move the parameter into a capture variable:
auto lambda = [bar, i3cp = std::move(i3)](){bar->foo3(i3cp);}but this won't compile either, because your function takes a parameter by value, so there's another copy there. You have to move your parameter again:
auto i3 = std::make_unique<int>(3); auto lambda = [bar, i3cp = std::move(i3)](){bar->foo3(std::move(i3cp));}but then you have another problem, because lambdas are constant by default i.e. its captures are const too and not movable, so you have to make your lambda mutable:
auto lambda = [bar, i3cp = std::move(i3)]() mutable { bar->foo3(std::move(i3cp)); }This will work. Notice, that this lambda is non-copyable, because it captures a unique_ptr, which itself is non-copyable, yet you can still use that lambda in
invokeMethod. That's becauseinvokeMethodwill move it if it needs to (e.g. when scheduled on another thread's queue).But all in all moving unique_ptrs around like this is kinda shady. It's hard to keep track of what's where and who holds it. I gave you a solution, but I would advise against using it.
-
T Tarae has marked this topic as solved on
-
@Tarae Your example does not show a problem with
invokeMethodbut with your usage of lambda. Even withoutinvokeMethodthis won't work, because you're trying to capturei3by copy and unique_ptr is not copyable:auto i3 = std::make_unique<int>(3); auto lambda = [bar, i3](){bar->foo3(i3);} // nope, can't doYou need to move the parameter into a capture variable:
auto lambda = [bar, i3cp = std::move(i3)](){bar->foo3(i3cp);}but this won't compile either, because your function takes a parameter by value, so there's another copy there. You have to move your parameter again:
auto i3 = std::make_unique<int>(3); auto lambda = [bar, i3cp = std::move(i3)](){bar->foo3(std::move(i3cp));}but then you have another problem, because lambdas are constant by default i.e. its captures are const too and not movable, so you have to make your lambda mutable:
auto lambda = [bar, i3cp = std::move(i3)]() mutable { bar->foo3(std::move(i3cp)); }This will work. Notice, that this lambda is non-copyable, because it captures a unique_ptr, which itself is non-copyable, yet you can still use that lambda in
invokeMethod. That's becauseinvokeMethodwill move it if it needs to (e.g. when scheduled on another thread's queue).But all in all moving unique_ptrs around like this is kinda shady. It's hard to keep track of what's where and who holds it. I gave you a solution, but I would advise against using it.
@Chris-Kawa Thank you for the explanation and the solution.
But I would argue that using unique_ptr is making it harder to keep track of ownership. The one holding the unique_ptr holds the data, it's simple as that.