Как обмануть boost:: asio, чтобы позволить обработчики только для перемещения

В протоколе связи RPC после вызова метода я отправляю "сделанные" сообщения обратно вызывающему. Поскольку методы вызываются одновременно, буфер, содержащий ответ (a std::string), должен быть защищен мьютексом. То, что я пытаюсь достичь, следующее:

void connection::send_response()
{
    // block until previous response is sent
    std::unique_lock<std::mutex> locker(response_mutex_);

    // prepare response
    response_ = "foo";

    // send response back to caller. move the unique_lock into the binder
    // to keep the mutex locked until asio is done sending.
    asio::async_write(stream_,
                      asio::const_buffers_1(response_.data(), response_.size()),
                      std::bind(&connection::response_sent, shared_from_this(),
                                _1, _2, std::move(locker))
                      );
}

void connection::response_sent(const boost::system::error_code& err, std::size_t len)
{
    if (err) handle_error(err);
    // the mutex is unlocked when the binder is destroyed
}

Однако это не скомпилируется, так как boost::asio требует обработчиков CopyConstructible.

Проблема может быть решена (хотя и не очень элегантно) с использованием следующего класса разделяемых кладовщиков вместо unique_lock:

template <typename Mutex>
class shared_lock
{
public:
    shared_lock(Mutex& m)
    : p_(&m, std::mem_fn(&Mutex::unlock))
    { m.lock(); }

private:
    std::shared_ptr<Mutex> p_;
};

В чем причина boost::asio отсутствия разрешений только для перемещения?

Ответы

Ответ 1

До тех пор, пока Крис Коххофф не ответит на ошибку, которую я подал, вот простой способ:

template <typename F>
struct move_wrapper : F
{
    move_wrapper(F&& f) : F(std::move(f)) {}

    move_wrapper(move_wrapper&&) = default;
    move_wrapper& operator=(move_wrapper&&) = default;

    move_wrapper(const move_wrapper&);
    move_wrapper& operator=(const move_wrapper&);
};

template <typename T>
auto move_handler(T&& t) -> move_wrapper<typename std::decay<T>::type>
{
    return std::move(t);
}

Обертка объявляет конструктор копирования, обманывая механизм asio в представлении, но никогда не определяет его, так что копирование приведет к ошибке связывания.

Теперь можно сделать следующее:

std::packaged_task<int()> pt([] {
    std::this_thread::sleep_for(std::chrono::seconds(1));
    return 42;
});
std::future<int> fu = pt.get_future();

boost::asio::io_service io;
io.post(move_handler(pt));
std::thread(&boost::asio::io_service::run, &io).detach();

int result = fu.get();
assert(result == 42);

Ответ 2

Здесь более простой способ:

shared_ptr<mutex> lock(mutex & m)
{
    m.lock();
    return shared_ptr<mutex>(&m, mem_fn(&mutex::unlock));
}

Не нужно писать пользовательские обертки.

Обращаясь к методам программирования Smart Pointer, вы можете даже использовать:

class shared_lock    {
private:    
    shared_ptr<void> pv;    
public:    
    template<class Mutex> explicit shared_lock(Mutex & m): pv((m.lock(), &m), mem_fn(&Mutex::unlock)) {}
};

shared_lock теперь можно использовать как:

shared_lock lock(m);

Обратите внимание, что shared_lock не является шаблоном для типа mutex, благодаря shared_ptr<void> способности скрывать информацию о типе.

Это потенциально может стоить дороже, но у него есть кое-что для него (приемник может принимать shared_lock, и вы можете передать ему обновляемый, общий, уникальный замок, scope_guard, в основном любой BasicLockable или "лучше"