Подтвердить что ты не робот

Shared_from_this вызывает bad_weak_ptr

Я пытаюсь сохранить список подключенных клиентов в asio. Я адаптировал пример сервера чата из документов (http://www.boost.org/doc/libs/1_57_0/doc/html/boost_asio/example/cpp03/chat/chat_server.cpp), и здесь важная часть того, что у меня получилось:

#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() довольно специфичен, но я не могу найти точно, что мне нужно изменить.

4b9b3361

Ответ 1

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

Ошибка заключается в том, что вы используете 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

Ответ 2

Ошибка заключается в том, что вы используете 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);
}

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

Ответ 3

// Do not forget to ----v---- publicly inherit :)
class tcp_connection : public boost::enable_shared_from_this<tcp_connection>