Search code examples
c++pointersboostshared-ptrboost-bind

Automatically delete containers sent to asynchronous functions/io_service


I would like to use an unordered_map as a job or session context object. So, I would like to allocate in some function bundle it with a static function in a function object and send this function object to an io_service. And obviously, I do not worry about deallocating it.

Any ideas on how to do that?

Thank you!

#include <iostream>
#include <unordered_map>
#include "boost/asio.hpp"
#include "boost/thread.hpp"

using namespace std;
namespace asio = boost::asio;

typedef std::unique_ptr<asio::io_service::work> work_ptr;
typedef boost::function<void(void) > boost_void_void_fun;

class job_processor {
public:

    job_processor(int threads) : thread_count(threads) {
        service = new asio::io_service();
        work = new work_ptr(new asio::io_service::work(*(service)));
        for (int i = 0; i < this->thread_count; ++i)
            workers.create_thread(boost::bind(&asio::io_service::run, service));
    }

    void post_task(boost_void_void_fun job) {
        this->service->post(job);
    }

    void drain() {
        this->work->reset();
    }

    void wait() {
        this->workers.join_all();
    }
private:
    int thread_count;
    work_ptr * work;
    asio::io_service* service;
    boost::thread_group workers;
};

typedef std::unordered_map<string, unsigned long> map_t;

class with_static_function {
public:

    static void print_map(map_t map) {
        for(map_t::iterator it = map.begin(); it != map.end(); ++it)
            std::cout << it->first << ":" << it->second << std::endl;
    }

    static void print__heap_map(map_t* map) {
        if(!map) return;
        for(map_t::iterator it = map->begin(); it != map->end(); ++it)
            std::cout << it->first << ":" << it->second << std::endl;
    }    
};

int main(int argc, char** argv) {
    map_t words;
    words["one"] = 1;

    // pass the reference;
    with_static_function::print_map(words);
    job_processor *pr = new job_processor(4);
    {
        map_t* heap_map = new map_t;
        (*heap_map)["two"] = 2;
        // I need this variable to the job_processor;
        // and I do not want to worry about deallocation.
        // should happen automatically somehow.
        // I am ok with changing the variable to be a shared_ptr or
        // anything else that works.    
        boost_void_void_fun fun = boost::bind(
                &with_static_function::print__heap_map,
                heap_map);
        fun(); // if binding was done right this should have worked.
        pr->post_task(fun);
    }

    pr->drain();
    pr->wait();

    delete pr;

    return 0;
}

Solution

  • A number of observations:

    1. Stop Emulating Java. Do not use new unless you're implementing an ownership primitive (smart handle/pointer type). Specifically, just create a pr:

      job_processor pr(4);
      

      Same goes for all the members of job_processor (you were leaking everything, and if job_processor were copied, you'd get double-free Undefined Behaviour

    2. The code

      // pass the reference;
      with_static_function::print_map(words);
      

      passes by value... meaning the whole map is copied

    3. to avoid that copy, fix the print_map signature:

      static void print_map(map_t const& map) {
          for(map_t::const_iterator it = map.begin(); it != map.end(); ++it)
              std::cout << it->first << ":" << it->second << std::endl;
      }
      

      Of course, consider just writing

      static void print_map(map_t const& map) {
          for(auto& e : map)
              std::cout << e.first << ":" << e.second << "\n";
      }
      
    4. The "heap" overload of that could be, as my wording implies, an overload. Be sure to remove the useless duplication of code (!):

      static void print_map(map_t const* map) {
          if (map) print_map(*map);
      }
      
    5. You don't even need that overload because you can simply use a lambda to bind (instead of boost::bind):

      auto heap_map = boost::make_shared<map_t>();
      heap_map->insert({{"two", 2}, {"three", 3}});
      
      boost_void_void_fun fun = [heap_map] { with_static_function::print_map(*heap_map); };
      

    Complete working program:

    Live On Coliru

    #include <iostream>
    #include <unordered_map>
    #include <boost/asio.hpp>
    #include <boost/make_shared.hpp>
    #include <boost/thread.hpp>
    #include <boost/function.hpp>
    #include <boost/bind.hpp>
    
    namespace asio = boost::asio;
    
    typedef boost::function<void(void)> boost_void_void_fun;
    
    class job_processor {
    public:
    
        job_processor(int threads) : service(), work(boost::asio::io_service::work(service))
        {
            for (int i = 0; i < threads; ++i)
                workers.create_thread(boost::bind(&asio::io_service::run, &service));
        }
    
        void post_task(boost_void_void_fun job) {
            service.post(job);
        }
    
        void drain() {
            work.reset();
        }
    
        void wait() {
            workers.join_all();
        }
    private:
        asio::io_service service;
        boost::optional<asio::io_service::work> work;
        boost::thread_group workers;
    };
    
    typedef std::unordered_map<std::string, unsigned long> map_t;
    
    namespace with_static_function {
        static void print_map(map_t const& map) {
            for(auto& e : map)
                std::cout << e.first << ":" << e.second << "\n";
        }
    }
    
    int main() {
        // pass the reference;
        with_static_function::print_map({ { "one", 1 } });
    
        job_processor pr(4);
        {
            auto heap_map = boost::make_shared<map_t>();
            heap_map->insert({{"two", 2}, {"three", 3}});
    
            boost_void_void_fun fun = [heap_map] { with_static_function::print_map(*heap_map); };
            pr.post_task(fun);
        }
    
        pr.drain();
        pr.wait();
    }
    

    Prints

    one:1
    three:3
    two:2