shared_from_this вызывает bad_weak_ptr

19

Я пытаюсь сохранить список подключенных клиентов в asio. Я адаптировал пример сервера чата из документов ( Ссылка ), и вот важная часть того, что у меня получилось:

#include <iostream>
#include <boost/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/asio.hpp>
#include <set>

using boost::asio::ip::tcp;

class tcp_connection;

std::set<boost::shared_ptr<tcp_connection>> clients;

void add_client(boost::shared_ptr<tcp_connection> client)
{
    clients.insert(client);
}

class tcp_connection : public boost::enable_shared_from_this<tcp_connection>
{
public:
    tcp_connection(boost::asio::io_service& io_service) : socket_(io_service)
    {
    }

    tcp::socket socket_;

    void start()
    {
        add_client(shared_from_this());
    }

    tcp::socket& socket()
    {
        return socket_;
    }
};

class tcp_server
{
public:
    tcp_server(boost::asio::io_service& io_service)
        : io_service_(io_service),
        acceptor_(io_service, tcp::endpoint(tcp::v4(), 6767))
    {
        tcp_connection* new_connection = new tcp_connection(io_service_);
        acceptor_.async_accept(new_connection->socket(),
                             boost::bind(&tcp_server::start_accept, this, new_connection,
                                         boost::asio::placeholders::error));
    }

private:
    void start_accept(tcp_connection* new_connection,
                      const boost::system::error_code& error)
    {
        if (!error)
        {
            new_connection->start();
            new_connection = new tcp_connection(io_service_);
            acceptor_.async_accept(new_connection->socket(),
                                   boost::bind(&tcp_server::start_accept, this, new_connection,
                                               boost::asio::placeholders::error));
        }
    }

    boost::asio::io_service& io_service_;
    tcp::acceptor acceptor_;
};

int main()
{
    try
    {
        boost::asio::io_service io_service;
        tcp_server server(io_service);
        io_service.run();
    }
    catch (std::exception& e)
    {
        std::cerr << "Exception: " << e.what() << "\n";
    }

    return 0;
}

При вызове shared_from_this мой сервер выходит из строя с сообщением «Exception: tr1 :: bad_weak_ptr». Я сделал несколько поисков, и кажется, что shared_from_this() довольно специфично, но я не могу найти то, что мне нужно изменить.

    
задан chrisvj 30.12.2014 в 01:24
источник
  • Почему вы сохраняете результат нового в необработанном указателе только для использования shared_from_this () позже? Кажется, ваш дизайн может быть упрощен, чтобы полностью устранить эту проблему. –  John Zwinck 30.12.2014 в 01:32
  • Подготовительные документы для enable_shared_from_this say Должно существовать хотя бы один экземпляр shared_ptr p, которому принадлежит t, которого у вас нет. –  Jonathan Potter 30.12.2014 в 01:33
  • @JonathanPotter Я это читал, но я этого не понимаю. –  chrisvj 30.12.2014 в 01:34
  • @chrisvj Моя интерпретация - вам нужно уже иметь shared_ptr, который содержит объект, прежде чем вы сможете больше использовать shared_from_this. Никогда не использовал его сам, хотя это просто предположение. –  Jonathan Potter 30.12.2014 в 01:36
  • @JonathanPotter Хм, не уверен, не вижу ничего с участием shared_ptr в примере, который у меня нет. –  chrisvj 30.12.2014 в 01:40
Показать остальные комментарии

2 ответа

22

Существенный анализ Джона Звинка определяется следующим образом:

  

Ошибка заключается в том, что вы используете shared_from_this () для объекта, который не имеет shared_ptr, указывающего на него. Это нарушает предварительное условие shared_from_this (), а именно, что по крайней мере один shared_ptr должен быть уже создан (и все еще существует), указывая на это.

Тем не менее, его совет кажется совершенно непонятным и опасным в коде Asio.

Вы должны решить эту проблему, - в первую очередь, не обрабатывая исходные указатели на tcp_connection , но всегда используя shared_ptr .

boost::bind имеет удивительную функцию, которая связывает shared_ptr<> просто отлично, поэтому она автоматически сохраняет объект, указывающий на объект, до тех пор, пока на нем работает какая-то асинхронная операция.

Это - в вашем примере кода - означает, что вам не нужен вектор clients , идущий противоположным образом от ответа Джона:

void start_accept()
{
    tcp_connection::sptr new_connection = boost::make_shared<tcp_connection>(io_service_);
    acceptor_.async_accept(new_connection->socket(),
            boost::bind(
                &tcp_server::handle_accept,
                this, new_connection, asio::placeholders::error
            )
        );
}

void handle_accept(tcp_connection::sptr client, boost::system::error_code const& error)
{
    if (!error)
    {
        client->start();
        start_accept();
    }
}

Я включил образец, который заставляет tcp_connection выполнять какую-то тривиальную работу (каждый раз клики записывают «hello world» клиенту, пока клиент не отключит соединение. Когда это произойдет, вы увидите деструктор tcp_connection :

Live On Coliru

#include <iostream>
#include <boost/bind.hpp>
#include <boost/make_shared.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/asio.hpp>
#include <boost/thread.hpp>

namespace asio = boost::asio;
using asio::ip::tcp;

class tcp_connection : public boost::enable_shared_from_this<tcp_connection>
{
public:
    typedef boost::shared_ptr<tcp_connection> sptr;

    tcp_connection(asio::io_service& io_service) : socket_(io_service), timer_(io_service)
    {
    }

    void start()
    {
        std::cout << "Created tcp_connection session\n";

        // post some work bound to this object; if you don't, the client gets
        // 'garbage collected' as the ref count goes to zero
        do_hello();
    }

    ~tcp_connection() {
        std::cout << "Destroyed tcp_connection\n";
    }

    tcp::socket& socket()
    {
        return socket_;
    }

  private:
    tcp::socket socket_;
    asio::deadline_timer timer_;

    void do_hello(boost::system::error_code const& ec = {}) {
        if (!ec) {
            asio::async_write(socket_, asio::buffer("Hello world\n"),
                    boost::bind(&tcp_connection::handle_written, shared_from_this(), asio::placeholders::error, asio::placeholders::bytes_transferred)
                );
        }
    }

    void handle_written(boost::system::error_code const& ec, size_t /*bytes_transferred*/) {
        if (!ec) {
            timer_.expires_from_now(boost::posix_time::seconds(1));
            timer_.async_wait(boost::bind(&tcp_connection::do_hello, shared_from_this(), asio::placeholders::error));
        }
    }
};

class tcp_server
{
public:
    tcp_server(asio::io_service& io_service)
        : io_service_(io_service),
          acceptor_(io_service, tcp::endpoint(tcp::v4(), 6767))
    {
        start_accept();
    }

private:
    void start_accept()
    {
        tcp_connection::sptr new_connection = boost::make_shared<tcp_connection>(io_service_);
        acceptor_.async_accept(new_connection->socket(),
                boost::bind(
                    &tcp_server::handle_accept,
                    this, new_connection, asio::placeholders::error
                )
            );
    }

    void handle_accept(tcp_connection::sptr client, boost::system::error_code const& error)
    {
        if (!error)
        {
            client->start();
            start_accept();
        }
    }

    asio::io_service& io_service_;
    tcp::acceptor acceptor_;
};

int main()
{
    try
    {
        asio::io_service io_service;
        tcp_server server(io_service);

        boost::thread(boost::bind(&asio::io_service::run, &io_service)).detach();

        boost::this_thread::sleep_for(boost::chrono::seconds(4));
        io_service.stop();
    }
    catch (std::exception& e)
    {
        std::cerr << "Exception: " << e.what() << "\n";
    }
}

Типичный выход:

[email protected]:/tmp$ time (./test& (for a in {1..4}; do nc 127.0.0.1 6767& done | nl&); sleep 2; killall nc; wait)
Created tcp_connection session
Created tcp_connection session
     1  Hello world
Created tcp_connection session
     2  Hello world
Created tcp_connection session
     3  Hello world
     4  Hello world
     5  Hello world
     6  Hello world
     7  Hello world
     8  Hello world
     9  Hello world
    10  Hello world
    11  Hello world
    12  Hello world
    13  
Destroyed tcp_connection
Destroyed tcp_connection
Destroyed tcp_connection
Destroyed tcp_connection
Destroyed tcp_connection

real    0m4.003s
user    0m0.000s
sys 0m0.015s
    
ответ дан sehe 30.12.2014 в 14:01
источник
7

Ошибка заключается в том, что вы используете shared_from_this() для объекта, у которого нет shared_ptr , указывающего на него. Это нарушает предварительное условие shared_from_this() , а именно, что по крайней мере один shared_ptr должен уже был создан (и все еще существует), указывая на this .

Коренной причиной ваших проблем является тот факт, что вы сначала сохраняете результат new в исходном указателе. Вы должны сохранить результат new в интеллектуальном указателе (всегда, в основном). Возможно, вы можете сразу сохранить смарт-указатель в списке clients .

Еще один подход, который я упомянул в комментариях, - полностью отказаться от использования shared_from_this() . Вам это не нужно. Что касается этого бита кода, о котором вы упомянули:

if ((boost::asio::error::eof == ec) || (boost::asio::error::connection_reset == ec))
{
    clients.erase(shared_from_this());
}

Вы можете заменить его на:

if ((boost::asio::error::eof == ec) || (boost::asio::error::connection_reset == ec))
{
    boost::shared_ptr<tcp_connection> victim(this, boost::serialization::null_deleter());
    clients.erase(victim);
}

То есть создайте «немой» умный указатель, который никогда не освободится ( Ссылка ), но который даст вам то, что вам нужно удалить его из списка клиентов. Существуют и другие способы сделать это, например, путем поиска std::set с помощью функции сравнения, которая принимает один shared_ptr и один необработанный указатель и знает, как сравнивать адреса, на которые они указывают. Неважно, какой вы выбираете, но вы полностью избегаете ситуации shared_from_this() .

    
ответ дан John Zwinck 30.12.2014 в 02:20
источник
  • Общая ситуация в этой ситуации является идиоматичной для управления сессиями в Boost Asio. Цель состоит в том, чтобы упростить управление асинхронными сеансами на протяжении всей жизни. На самом деле клиенты, установленные здесь, делают это очень сложно, так как практически невозможно получить очистку прямо со всеми местами, где асинхронные операции могут потерпеть неудачу (даже не думая об общей безопасности исключений) –  sehe 30.12.2014 в 14:04
  • @sehe: звучит разумно, спасибо за это. –  John Zwinck 30.12.2014 в 14:38