The problem is with your declaration of the aClass member in B:
class B {
public:
A aClass;
explicit B(const A& a) : aClass(a) {}
...
The aClass member is of type A, so when you do aClass(a), a copy is performed. You have defined a copy constructor for A, but it does not copy the already opened socket, leaving you with a default-initialized (and therefore unopened) socket in the copy.
Consider the following example class:
class C {
public:
std::string foo_ = "foo";
C(const std::string &foo) : foo_{foo} {}
C(const C &c) {}
};
If you would run the following code:
C c{"bar"};
C c2{c};
std::cout << c2.foo_ << std::endl;
You would get the output foo. This is because the copy constructor for C does not copy the foo_ member. Therefore, when c2 is initialized, it will not take the value bar from c, but instead default-initialize the foo_ member to foo. The same problem applies to the socket member of your A class.
Solution 1
Avoid a copy by making B hold a pointer to to an A:
class B {
public:
A* aClass;
explicit B(A* a) : aClass(a) {}
void getData() {
std::array<char, 1024> recv_buffer{};
aClass->socketInstance.receive_from(boost::asio::buffer(recv_buffer),
aClass->endpointSetup);
}
};
And then in main do:
B b(&a);
This way, when you create a B, it will still have access to the socket you initialized for the original A.
Instead of using A*, you could also use a smart pointer, i.e. std::unique_ptr<A> or std::shared_ptr<A>, if you want to express ownership in the sense that the B owns the A, and thereby the socket held by A.
Solution 2
An alternative solution would be to move-construct A, if you want to pass an A to B but also want B to take ownership of the socket held by the A instance, without using the indirection of a (smart) pointer.
This way you could take advantage of the move-constructor for the udp::socket. However, this would require you to move the io_context out of A, because the io_context does not have move-constructor.
#include <boost/asio.hpp>
#include <boost/asio/io_context.hpp>
using namespace boost::asio::ip;
class A {
public:
std::string address;
uint16_t port;
boost::asio::io_context &io_context;
udp::socket socketInstance;
udp::endpoint endpointSetup;
explicit A(boost::asio::io_context &io_context, std::string addrs,
uint16_t port)
: io_context{io_context}, address{addrs}, port{port}, socketInstance{
io_context} {}
void attachListener() {
endpointSetup = udp::endpoint{make_address(address), port};
socketInstance = udp::socket{io_context, endpointSetup};
}
};
class B {
public:
A aClass;
explicit B(A &&a) : aClass{std::move(a)} {}
void getData() {
std::array<char, 1024> recv_buffer{};
aClass.socketInstance.receive_from(boost::asio::buffer(recv_buffer),
aClass.endpointSetup);
}
};
int main() {
boost::asio::io_context io_context{};
A a{io_context, "127.0.0.1", 5080};
a.attachListener();
B b{std::move(a)};
b.getData();
}
Both solutions can be tested e.g. with
netcat --udp localhost 5080