Using class member in separate thread or äquivalent of overwriting run in C++

I am currently trying to run a class member function in a separate thread which results in a call to implicitly deleted copy constructor , there are already multiple Questions to this answered here on StackOverflow but honestly a lot of this is 5+ years old or feels hacky thats why I am asking myself what would be the best practice to do this with modern c++?

So currently I have the following :

A ZMQWorker which should run in separate threads ( sock is not Thread save)

zmqwoker.cpp

#include "zmqworker.h"
#include <QDebug>
#include <iostream>

ZMQWorker::ZMQWorker()
    : sock(ctx, zmq::socket_type::dealer)
{
      qDebug() << "Dealer Socket created";

}

void ZMQWorker::connectSocket()
{
    std::string origin="CPP_ZMQ";
    sock.connect("tcp://127.0.0.1:5555");
    qDebug() << "Dealer Socket connected";

}

void ZMQWorker::receiveMessage()
{

    while (1){
        std::vector<zmq::message_t> recv_msg;
        auto res = zmq::recv_multipart(sock, 
        std::back_inserter(recv_msg));
        
        for (auto&& msg : recv_msg) {
            std::cout <<  msg.to_string_view() << std::endl;

        }
    }

}

zmq::socket_t &ZMQWorker::getSock()
{
    return sock;
}

zmqworker.h


#ifndef ZMQWORKER_H
#define ZMQWORKER_H
#include <zmq_addon.hpp>
#include <thread>



class ZMQWorker
{

public:
    ZMQWorker();
    void connectSocket();
    zmq::context_t ctx;
    zmq::socket_t sock;
    void receiveMessage();



    zmq::socket_t &getSock();

};

#endif // ZMQWORKER_H

and a ZMQBridge which should act as Bridge between QT and the ZMQ socket , since receive and send both are blocking function these should work in different Threads.

zmqbridge.h


#ifndef ZMQBRIDGE_H
#define ZMQBRIDGE_H

#include <QObject>
#include <iostream>

#include "zmqworker.h"


class ZMQBridge : public QObject
{
    Q_OBJECT
public:
    explicit ZMQBridge(QObject *parent = nullptr);
    void createMessageSocket();

    ZMQWorker sendSocket;
    Q_INVOKABLE void callCoro(QString msg);


signals:


private:
    void spawnWorker();

};

#endif // ZMQBRIDGE_H

zmqbridge.cpp

#include "zmqbridge.h"
#include <stdio.h>
#include <QDebug>
#include "zmq_dealer.h"
#include <iostream>
#include <msgpack.hpp>
#include <thread>
#include <chrono>
#include <iostream>
#include <string>
#include <random>
#include <nlohmann/json.hpp>

ZMQBridge::ZMQBridge(QObject *parent)
    : QObject{parent},

      sendSocket()
{
      sendSocket.connectSocket();
}

void ZMQBridge::createMessageSocket(){}

void ZMQBridge::spawnWorker(){}

void  ZMQBridge::callCoro(QString msg)
{
std::cout <<  "Hello from c++";

ZMQWorker receiveSocket = ZMQWorker();
std::thread (&ZMQWorker::receiveMessage, receiveSocket).detach();

qDebug() << "Hello";
nlohmann::json jmsg;
jmsg["randvar"] = "Hello";
zmq::message_t z_out(jmsg.dump());
sendSocket.getSock().send(z_out, zmq::send_flags::none);

}

Error Message:

/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.1.sdk/usr/include/c++/v1/type_traits:2596: error: call to implicitly-deleted copy constructor of 'typename decay<ZMQWorker &>::type' (aka 'ZMQWorker')
In file included from /Users/ahoehne/repos/ondoki-desktop/zmqbridge.cpp:1:
In file included from /Users/ahoehne/repos/ondoki-desktop/zmqbridge.h:4:
In file included from /Users/ahoehne/Qt/6.2.2/macos/lib/QtCore.framework/Headers/QObject:1:
In file included from /Users/ahoehne/Qt/6.2.2/macos/lib/QtCore.framework/Headers/qobject.h:46:
In file included from /Users/ahoehne/Qt/6.2.2/macos/lib/QtCore.framework/Headers/qobjectdefs.h:48:
In file included from /Users/ahoehne/Qt/6.2.2/macos/lib/QtCore.framework/Headers/qnamespace.h:44:
In file included from /Users/ahoehne/Qt/6.2.2/macos/lib/QtCore.framework/Headers/qglobal.h:45:
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.1.sdk/usr/include/c++/v1/type_traits:2596:12: error: call to implicitly-deleted copy constructor of 'typename decay<ZMQWorker &>::type' (aka 'ZMQWorker')
    return _VSTD::forward<_Tp>(__t);
           ^~~~~~~~~~~~~~~~~~~~~~~~
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.1.sdk/usr/include/c++/v1/__config:856:15: note: expanded from macro '_VSTD'
#define _VSTD std::_LIBCPP_ABI_NAMESPACE
              ^
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.1.sdk/usr/include/c++/v1/thread:312:28: note: in instantiation of function template specialization 'std::__decay_copy<ZMQWorker &>' requested here
                    _VSTD::__decay_copy(_VSTD::forward<_Args>(__args))...));
                           ^
/Users/ahoehne/repos/ondoki-desktop/zmqbridge.cpp:31:1: note: in instantiation of function template specialization 'std::thread::thread<void (ZMQWorker::*)(), ZMQWorker &, void>' requested here
std::thread (&ZMQWorker::receiveMessage, receiveSocket).detach();
^
/Users/ahoehne/repos/ondoki-desktop/zmqworker.h:14:20: note: copy constructor of 'ZMQWorker' is implicitly deleted because field 'ctx' has a deleted copy constructor
    zmq::context_t ctx;
                   ^
/Users/ahoehne/repos/vcpkg/installed/arm64-osx/include/zmq.hpp:903:5: note: 'context_t' has been explicitly marked deleted here
    context_t(const context_t &) ZMQ_DELETED_FUNCTION;
    ^

In other languages like Java or Python which I am used to , I would just inherit from Thread and overwrite the run method and maybe depending on how dynamic and many I need use this with a ThreadPool combined with async/await ...How would I do this in C++ .The informations seems to be mixed and it feels like following a rabbit into it's hole to a new magic land.

Thank you for your help ....


Solution 1:

You're passing receiveSocket by value trying to call the deleted copy constructor. You have to either pass a pointer to your ZMQWorker:

std::thread (&ZMQWorker::receiveMessage, &receiveSocket)

or a reference:

std::thread (&ZMQWorker::receiveMessage, std::ref(receiveSocket))

But then you have to solve another problem:

ZMQWorker receiveSocket;

goes out of scope at the end of this method and will be destroyed while your other thread might still be using it.

Solution 2:

What I did now is this :

zmqbridge.cpp


#include "zmqbridge.h"
#include <stdio.h>
#include <QDebug>
#include "zmq_dealer.h"
#include <iostream>
#include <msgpack.hpp>
#include <thread>
#include <chrono>
#include <iostream>
#include <string>
#include <random>
#include <nlohmann/json.hpp>

ZMQBridge::ZMQBridge(QObject *parent)
    : QObject{parent},

      sendSocket("CPP_SEND"),
      receiveSocket("CPP_RECV")
{
      this->sendSocket.connectSocket();
      this->receiveSocket.connectSocket();

      std::thread (&ZMQWorker::receiveMessage, &receiveSocket).detach();
}

void ZMQBridge::createMessageSocket(){}

void ZMQBridge::spawnWorker(){}

void  ZMQBridge::callCoro(QString msg)
{
std::cout <<  "Hello from c++";



qDebug() << "Hello";
nlohmann::json jmsg;
jmsg["name"] = "hello";
jmsg["dispatch_to"] = "caller";

zmq::message_t z_out(jmsg.dump());
sendSocket.getSock().send(z_out, zmq::send_flags::none);

}

zmqbridge.h


#ifndef ZMQBRIDGE_H
#define ZMQBRIDGE_H

#include <QObject>
#include <iostream>

#include "zmqworker.h"


class ZMQBridge : public QObject
{
    Q_OBJECT
public:
    explicit ZMQBridge(QObject *parent = nullptr);
    void createMessageSocket();

    ZMQWorker sendSocket;
    ZMQWorker receiveSocket;
    Q_INVOKABLE void callCoro(QString msg);


signals:


private:
    void spawnWorker();

};

#endif // ZMQBRIDGE_H

This works but it does not feel like a ideal grade solution , if so I will accept the answer provided by Stefan Riedel