4

I am following Boost UDP multicast sender tutorial here . I modify it to make a class as follow:

#define _CRT_SECURE_NO_WARNINGS
#include <ctime>
#include <iostream>
#include <string>
#include <boost/array.hpp>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/asio.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/thread.hpp>
using boost::asio::ip::udp;
using std::cout;
using std::cin;
using std::endl;

class sender
{
private:
    boost::asio::io_context io_context;
    boost::asio::ip::udp::endpoint endpoint_;
    boost::asio::ip::udp::socket socket_;
    int message_count_;
    std::string message_;
    bool showBroadcast;

public:
    // constructor
    sender(std::string multicast_address, unsigned short multicast_port, bool show = true)
    {
        boost::asio::io_context io_context;
        boost::asio::ip::udp::endpoint endpoint_(boost::asio::ip::make_address("192.168.0.255"), 13000);
        boost::asio::ip::udp::socket socket_(io_context, endpoint_.protocol());
        socket_.set_option(boost::asio::ip::udp::socket::reuse_address(true));  // no need
    }

    // destructor
    ~sender()
    {
        cout << "UDP sender exiting." << endl;
    }

private:
    std::string get_input()
    {
        std::string result;
        cout << "Enter your message: ";
        getline(cin, result);
        return result;
    }

    std::string make_daytime_string()
    {
        using namespace std; // For time_t, time and ctime;
        time_t now = time(0);
        std::string result = ctime(&now);
        return result.erase(result.length() - 1, 1);
    }

    std::string some_string()
    {
        std::string result;
        result = make_daytime_string();
        return result;
    }
};

int main(int argc, char* argv[])
{
    try
    {
        sender s("192.168.0.255", 13000);
    }
    catch (std::exception& e)
    {
        std::cerr << "Exception: " << e.what() << "\n";
    }

    return 0;
}

I wish to encapsulate io_context object inside the class, rather than having it outside. VC++ complains:

boost::asio::basic_datagram_socket': no appropriate default constructor available

I believe it is trying to force me to have the constructor as follow (which I try to move away from):

    sender(boost::asio::io_context& io_context, const boost::asio::ip::address& multicast_address, unsigned short multicast_port, bool show = true) 
    : endpoint_(multicast_address, multicast_port),
    socket_(io_context, endpoint_.protocol())

How can I possibly have everything encapsulate inside my class? Why does Boost force me to do the other way? Please help. Thank you so much.

This seems to be due to io_context being non-copyable as suggested here . I wish to have this class copyable. Any idea?

CaTx
  • 1,421
  • 4
  • 21
  • 42

1 Answers1

2

none of the ASIO classes are copyable and most hold an io_context reference so need to be constructed with one. If you want your classes to be copyable you need to use shared pointers to the ASIO objects. I'm not sure why you would want to copy ASIO objects though as you can't use them from more than one place at a time anyway.

Alan Birtles
  • 32,622
  • 4
  • 31
  • 60
  • how can I initialize one io_service/io_context object and keep re-using it from the same class? I looked at Sehe's example by dont quite see how to do it from there. – CaTx Mar 12 '18 at 22:27
  • And how can I properly dispose of that object if it is shared? – CaTx Mar 12 '18 at 22:27
  • Does it mean I have to use it this way? boost::shared_ptr socket; boost::shared_ptr service; – CaTx Mar 12 '18 at 22:29
  • 1
    You can use the io_context from multiple classes but it doesn't make much sense to share the sockets. You could statically/lazy initialise the context inside your class so that you have a single instance shared between your objects. It's much easier though to create a single context outside your sender class and pass it in to all the instances – Alan Birtles Mar 12 '18 at 23:49
  • Thank you, sir! I wonder why encapsulating an io_service / io_context inside a class deemed not a good practice? – CaTx Mar 12 '18 at 23:58
  • It's not bad practice as such just unusual, defining the io_context outside of your class means that your class only needs to deal with the actual communications and therefore makes your class smaller – Alan Birtles Mar 13 '18 at 06:36
  • First off, sorry for commenting on such an old issue.... I was looking to use the non-boost version of the ASIO library to create a software "component" which is responsible for communications with the outside world, i.e. receive remote requests from a UI etc... So, this "component" would interface with the ASIO library and to read/write data to/from the socket..... In this case, the application does not care about io_context, only the component using the ASIO library, so it makes sense to have it as a class member. – Matt Conway Oct 26 '18 at 11:06
  • I don't want my class to be copyable, I want it to construct and hold the reference to its own io_context and socket. I really don't understand this design choice by the boost developers. Why do I have to break encapsulation, construct the io_context outside of the class (in a calling function that doesn't know or care what an io_context is) and then pass in the reference/pointer? The other option is to use a pointer to a socket and delay constructing the actual socket until we want to connect. But then I have to have a separate connect() function and can't just connect inside the constructor. – muusbolla Jun 26 '19 at 05:42
  • @muusbolla its better to ask a new question if you have a question as it is difficult to give answers in comments. There is no need to create your `io_context` outside of your class. If only one class in your application is using ASIO then creating the `io_context` in the class constructor along with the other ASIO objects is perfectly fine and should work. – Alan Birtles Jun 26 '19 at 07:15
  • I am having exactly the same problem as the OP. You cannot initialize for example a socket and an io_context in the same constructor if they are both member variables that were not initialized outside the scope of the constructor call. `Foo::Foo() : context(), socket(context)` fails at runtime. The context is not initialized before being passed to the socket constructor. `Foo::Foo() : context() { socket = tcp::socket(context); }` does not compile, giving error "tcp::socket does not implement default constructor". – muusbolla Jun 26 '19 at 18:53
  • 2
    @muusbolla no, the OP wasn't having that problem. Pay attention to your compiler warnings which are probably saying `context` and `socket` are initialised in the wrong order, make sure `context` is declared before `socket`. – Alan Birtles Jun 26 '19 at 20:30
  • You are right. I reordered the objects in the class so that io_context came before socket in the class declaration and now I am able to construct them in the initalizer list. Thank you. – muusbolla Jun 27 '19 at 01:17