C++ shared_from_this 导致 bad_weak_ptr

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/27697973/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-28 20:50:58  来源:igfitidea点击:

shared_from_this causing bad_weak_ptr

c++boostshared-ptr

提问by chrisvj

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()很特别,但我似乎无法准确找到我需要更改的内容。

采纳答案by sehe

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() 的先决条件,即必须已经创建(并且仍然存在)至少一个指向 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_connectionin the first place but always using shared_ptrinstead.

您应该通过 - 实际上 - 首先不处理原始指针,tcp_connection而是始终使用shared_ptr来解决这个问题。

boost::bindhas 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 clientsvector, going the opposite way from John's answer:

这 - 在您的示例代码中 - 意味着您不需要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();
    }
}

I've included a sample that makes tcp_connectiondo 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_connectionoperation being run:

我已经包含了一个可以tcp_connection做一些琐碎工作的示例(它每秒循环向客户端写入“hello world”,直到客户端断开连接。当它断开连接时,您可以看到tcp_connection正在运行的操作的析构函数:

Live On Coliru

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

回答by John Zwinck

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

错误是您在shared_from_this()没有shared_ptr指向它的对象上使用。这违反了 的先决条件shared_from_this(),即至少shared_ptr必须已经创建(并且仍然存在)指向this

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

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

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://stackoverflow.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::setusing a comparison function which takes one shared_ptrand 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://stackoverflow.com/a/5233034/4323),但它将为您提供从客户端列表中删除它所需的内容。还有其他方法可以做到这一点,例如通过std::set使用比较函数进行搜索,该函数采用一个shared_ptr和一个原始指针并知道比较它们指向的地址。您选择哪种方式并不重要,但您可以shared_from_this()完全摆脱这种情况。

回答by franckspike

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