shared_from_this导致bad_weak_ptr

时间:2022-09-09 00:29:17

I am trying to keep a list of connected clients in asio. I have adapted the chat server example from the docs (http://www.boost.org/doc/libs/1_57_0/doc/html/boost_asio/example/cpp03/chat/chat_server.cpp) and here's the important part of what I ended up with:

我想在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;
}

At the call to shared_from_this, my server crashes with the message "Exception: tr1::bad_weak_ptr." I have done some searching and it appears shared_from_this() is pretty particular, but I can't seem to find exactly what I need to change.

在调用shared_from_this时,我的服务器崩溃并显示消息“异常:tr1 :: bad_weak_ptr”。我做了一些搜索,似乎shared_from_this()非常特别,但我似乎无法找到我需要更改的内容。

2 个解决方案

#1


23  

John Zwinck's essential analysis is spot on:

John Zwinck的基本分析是:

The bug is that you're using shared_from_this() on an object which has no shared_ptr pointing to it. This violates a precondition of shared_from_this(), namely that at least one shared_ptr must already have been created (and still exist) pointing to this.

问题是你在没有指向它的shared_ptr的对象上使用shared_from_this()。这违反了shared_from_this()的前提条件,即必须已经创建(并且仍然存在)至少一个指向此的shared_ptr。

However, his advice seems completely beside the point and dangerous in Asio code.

然而,他的建议似乎完全不同于Asio代码中的危险和危险。

You should solve this by - indeed - not handling raw pointers to tcp_connection in the first place but always using shared_ptr instead.

你应该通过 - 确实 - 首先不处理tcp_connection的原始指针但总是使用shared_ptr来解决这个问题。

boost::bind has the awesome feature that it binds to shared_ptr<> just fine so it automagically keeps the pointed to object alive as long as some asynchronous operation is operating on it.

boost :: bind有一个很棒的功能,它绑定到shared_ptr <>就好了,只要有一些异步操作在它上面运行它就会自动保持指向对象的活动状态。

This - in your sample code - means you don't need the clients vector, going the opposite way from John's answer:

这 - 在您的示例代码中 - 意味着您不需要客户端向量,与John的答案相反:

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();
    }
}

I've included a sample that makes tcp_connection do some trivial work (it loops writing 'hello world' to the client each second, until the client drops the connection. When it does, you can see the destructor of the tcp_connection operation being run:

我已经包含了一个使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";
    }
}

Typical output:

典型输出:

sehe@desktop:/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


8  

The bug is that you're using shared_from_this() on an object which has no shared_ptr pointing to it. This violates a precondition of shared_from_this(), namely that at least one shared_ptr must already have been created (and still exist) pointing to this.

问题是你在没有指向它的shared_ptr的对象上使用shared_from_this()。这违反了shared_from_this()的前提条件,即必须已经创建(并且仍然存在)至少一个指向此的shared_ptr。

The root cause of your troubles seems to be the fact that you're storing the result of new in a raw pointer initially. You should store the result of new in a smart pointer (always, basically). Perhaps you can store the smart pointer in your clients list straight away, then.

您遇到麻烦的根本原因似乎是您最初将新结果存储在原始指针中。您应该将新结果存储在智能指针中(总是基本上)。也许您可以立即将智能指针存储在客户列表中。

Another approach which I mentioned in the comments is to stop using shared_from_this() entirely. You don't need it. As for this bit of code you mentioned:

我在评论中提到的另一种方法是完全停止使用shared_from_this()。你不需要它。至于你提到的这段代码:

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

You can replace it by:

您可以将其替换为:

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);
}

That is, create a "dumb" smart pointer which will never deallocate (https://*.com/a/5233034/4323) but which will give you what you need to delete it from the list of clients. There are other ways to do it too, such as by searching the std::set using a comparison function which takes one shared_ptr and one raw pointer and knows to compare the addresses to which they point. It doesn't matter much which way you choose, but you escape the shared_from_this() situation entirely.

也就是说,创建一个永远不会解除分配的“哑”智能指针(https://*.com/a/5233034/4323),但它会为您提供从客户列表中删除它所需的内容。还有其他方法可以做到这一点,例如通过使用比较函数搜索std :: set,该函数接受一个shared_ptr和一个原始指针,并且知道比较它们指向的地址。你选择哪种方式并不重要,但你完全逃避了shared_from_this()情况。

#1


23  

John Zwinck's essential analysis is spot on:

John Zwinck的基本分析是:

The bug is that you're using shared_from_this() on an object which has no shared_ptr pointing to it. This violates a precondition of shared_from_this(), namely that at least one shared_ptr must already have been created (and still exist) pointing to this.

问题是你在没有指向它的shared_ptr的对象上使用shared_from_this()。这违反了shared_from_this()的前提条件,即必须已经创建(并且仍然存在)至少一个指向此的shared_ptr。

However, his advice seems completely beside the point and dangerous in Asio code.

然而,他的建议似乎完全不同于Asio代码中的危险和危险。

You should solve this by - indeed - not handling raw pointers to tcp_connection in the first place but always using shared_ptr instead.

你应该通过 - 确实 - 首先不处理tcp_connection的原始指针但总是使用shared_ptr来解决这个问题。

boost::bind has the awesome feature that it binds to shared_ptr<> just fine so it automagically keeps the pointed to object alive as long as some asynchronous operation is operating on it.

boost :: bind有一个很棒的功能,它绑定到shared_ptr <>就好了,只要有一些异步操作在它上面运行它就会自动保持指向对象的活动状态。

This - in your sample code - means you don't need the clients vector, going the opposite way from John's answer:

这 - 在您的示例代码中 - 意味着您不需要客户端向量,与John的答案相反:

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();
    }
}

I've included a sample that makes tcp_connection do some trivial work (it loops writing 'hello world' to the client each second, until the client drops the connection. When it does, you can see the destructor of the tcp_connection operation being run:

我已经包含了一个使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";
    }
}

Typical output:

典型输出:

sehe@desktop:/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


8  

The bug is that you're using shared_from_this() on an object which has no shared_ptr pointing to it. This violates a precondition of shared_from_this(), namely that at least one shared_ptr must already have been created (and still exist) pointing to this.

问题是你在没有指向它的shared_ptr的对象上使用shared_from_this()。这违反了shared_from_this()的前提条件,即必须已经创建(并且仍然存在)至少一个指向此的shared_ptr。

The root cause of your troubles seems to be the fact that you're storing the result of new in a raw pointer initially. You should store the result of new in a smart pointer (always, basically). Perhaps you can store the smart pointer in your clients list straight away, then.

您遇到麻烦的根本原因似乎是您最初将新结果存储在原始指针中。您应该将新结果存储在智能指针中(总是基本上)。也许您可以立即将智能指针存储在客户列表中。

Another approach which I mentioned in the comments is to stop using shared_from_this() entirely. You don't need it. As for this bit of code you mentioned:

我在评论中提到的另一种方法是完全停止使用shared_from_this()。你不需要它。至于你提到的这段代码:

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

You can replace it by:

您可以将其替换为:

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);
}

That is, create a "dumb" smart pointer which will never deallocate (https://*.com/a/5233034/4323) but which will give you what you need to delete it from the list of clients. There are other ways to do it too, such as by searching the std::set using a comparison function which takes one shared_ptr and one raw pointer and knows to compare the addresses to which they point. It doesn't matter much which way you choose, but you escape the shared_from_this() situation entirely.

也就是说,创建一个永远不会解除分配的“哑”智能指针(https://*.com/a/5233034/4323),但它会为您提供从客户列表中删除它所需的内容。还有其他方法可以做到这一点,例如通过使用比较函数搜索std :: set,该函数接受一个shared_ptr和一个原始指针,并且知道比较它们指向的地址。你选择哪种方式并不重要,但你完全逃避了shared_from_this()情况。