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