c++qtsmart-pointersunique-ptrqmetaobject

Passing std::unique_ptr as qt signal argument with std::move


I'm working in Qt, I didn't mention it, because I thought it was just C++ problem.

I solved this problem with shared pointers, so don't give me solution. But it's question of comprehending, I want to understand why it's not working.

I was trying the following:

test.h:

#include <QObject>
#include "response.h"
#include <memory>

class Test : public QObject
{
    Q_OBJECT
public:
    explicit Test(QObject *parent = nullptr);

signals:
    void signal(std::unique_ptr<Response> resp);

public slots:

};

test.cpp:

Test::Test(QObject *parent) : QObject(parent)
{
    std::unique_ptr<Response> response(new Response(5));

    emit signal(std::move(response));
}

Response class:

class Response
{
public:
    Response(int data);

private:
     int data;
};

I know that std::unique_ptr can't be copied and I use std::move here. But in spite of this I get an error:

moc_test.cpp:75:85: error: use of deleted function ‘std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = Response; _Dp = std::default_delete<Response>]’
         case 0: _t->signal((*reinterpret_cast< std::unique_ptr<Response>(*)>(_a[1]))); break;

In the following snippet of moc_test.cpp, which is product of Qt meta-object system:

void Test::qt_static_metacall(QObject *_o, QMetaObject::Call _c, int _id, void `_a)
{
    if (_c == QMetaObject::InvokeMetaMethod) {
        auto *_t = static_cast<Test *>(_o);
        Q_UNUSED(_t)
        switch (_id) {
//------- !! This line!!
        case 0: _t->signal((*reinterpret_cast< std::unique_ptr<Response>(*)>(_a[1]))); break;
        default: ;
        }
    } else if (_c == QMetaObject::IndexOfMethod) {
        int *result = reinterpret_cast<int *>(_a[0]);
        {
            using _t = void (Test::*)(std::unique_ptr<Response> );
            if (*reinterpret_cast<_t *>(_a[1]) == static_cast<_t>(&Test::signal)) {
                *result = 0;
                return;
            }
        }
    }
}

Spent much time, but I couldn't figure it out why that error occurred and didn't find anything helpful in the internet.


Solution

  • Creating Custom Qt Types mentions the constraints imposed on a Qt meta-type, i.e the type requires a copy constructor (to be emit-able).

    The reason that const is mentioned in the error message is that we're trying to invoke the (deleted) copy constructor of the unique_ptr (as indicated by the message).

    I suspect that while the move is taking place when passed as parameter, you're still assigning the rvalue (e.g in the constructor).

    Note, you haven't posted all the code, but my suspicion is that your code fails for the same reason as the code below:

    #include <memory>
    
    
    struct Foo {
        std::unique_ptr<int> pointer_;
    
        explicit Foo(std::unique_ptr<int> pointer) : pointer_(pointer) {
        }
    };
    
    int main() {
        std::unique_ptr<int> pointer(new int(10));
        Foo{move(pointer)};
    
    
        // your code goes here
        return 0;
    }
    

    The failure being:

    prog.cpp: In constructor ‘Foo::Foo(std::unique_ptr<int>)’:
    prog.cpp:8:63: error: use of deleted function ‘std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = int; _Dp = std::default_delete<int>]’
      explicit Foo(std::unique_ptr<int> pointer) : pointer_(pointer)
    

    The reason is that copy initialization is taking place (of pointer_). You need to call move to move the (now lvalue pointer) into the member.

    I would suggest the code perform the initialization to be modified as follows:

    struct Foo {
      std::unique_ptr<int> pointer_;
    
      explicit Foo(std::unique_ptr<int>&& pointer) 
      : pointer_(move(pointer)) { //**NOTE** the move here!!!
      }
    };
    

    Note that I did not pass std::unique_ptr by value. In practice it would always be an rvalue, and perhaps the additional move constructor call would be elided (I'm not sure, but I just use an rvalue reference in these circumstances).

    Also note that it is not necessary to qualify move, as it will looked up as result of ADL.