【问题标题】:shared_from_this causing bad_weak_ptrshared_from_this 导致 bad_weak_ptr
【发布时间】:2015-02-26 04:14:30
【问题描述】:

我正在尝试在 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() 时,我的服务器崩溃并显示以下消息:

例外:tr1::bad_weak_ptr

我进行了一些搜索,似乎 shared_from_this() 非常特别,但我似乎无法准确找到我需要更改的内容。

【问题讨论】:

  • 为什么将new 的结果存储在一个原始指针中,以便以后使用shared_from_this()?看来您的设计可以简化以完全消除这个问题。
  • enable_shared_from_this 的 boost 文档说 There must exist at least one shared_ptr instance p that owns t,你似乎没有。
  • @JonathanPotter 我读过,但我不明白。
  • @chrisvj 我的解释是你需要已经有一个shared_ptr 来保存对象,然后才能使用shared_from_this 进行更多操作。虽然我自己从未使用过,所以这只是一个猜测。
  • 只有在生命周期由共享指针管理的对象上调用shared_from_this 才有意义。否则,不可能有一个共享指针的生命周期至少与对象的生命周期一样长,而shared_from_this 的唯一目的就是返回这样的东西。所以,总而言之,你是在要求 shared_from_this 做不可能的事情。

标签: c++ boost shared-ptr c++-faq


【解决方案1】:
// Do not forget to ----v---- publicly inherit :)
class tcp_connection : public boost::enable_shared_from_this<tcp_connection>

【讨论】:

【解决方案2】:

John Zwinck 的基本分析如下:

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

然而,他的建议在 Asio 代码中似乎完全题外话和危险。

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

boost::bind 有一个很棒的功能,它可以很好地绑定到shared_ptr&lt;&gt;,所以只要对它进行一些异步操作,它就会自动使指向的对象保持活动状态。

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

典型输出:

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

【讨论】:

  • 很好的答案。但是,AFAIK 所有连接都存储在向量中的原因是服务器能够在退出时关闭它们(例如在信号处理程序中)。这就是我发现这个问题的方式——除了关闭套接字之外,我还清除了信号处理程序中的向量。你知道这个用例的无向量解决方案吗?
  • @DavidNemeskey 当然,如果您需要会话列表,请务必保留它(尽管使用weak_ptr)。是的,我想出了一个无矢量的解决方案,由于控制反转,它实际上更加通用:stackoverflow.com/questions/49394277/…(请注意答案集中在所有会话的不同操作上,但它也链接到其他答案关注关机顺序)
  • 我有 'bad_weak_ptr' 异常,因为我忘记在 'std::enable_shared_from_this()' 前面添加 'public'。
  • @Rostfrei 非常感谢!我已经疯狂地调试了 2 个小时,这是因为缺少 public
  • @ThekoLekena 我冒昧地应用了c++-faq 标签
【解决方案3】:

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

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

我在 cmets 中提到的另一种方法是完全停止使用 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);
}

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

【讨论】:

  • shared_from_this "situation" 是 Boost Asio 中会话管理的惯用语。它的目的是使异步会话的生命周期管理更容易。事实上,这里设置的clients 让这变得非常困难,因为几乎不可能正确清理所有异步操作可能失败的地方(甚至不考虑一般的异常安全)
猜你喜欢
  • 2020-01-02
  • 1970-01-01
  • 1970-01-01
  • 2018-10-23
  • 2018-06-03
  • 1970-01-01
  • 1970-01-01
  • 2019-05-22
  • 1970-01-01
相关资源
最近更新 更多