C++ 共享来自\u的\u导致错误\u弱\u ptr

C++ 共享来自\u的\u导致错误\u弱\u ptr,c++,boost,shared-ptr,c++-faq,C++,Boost,Shared Ptr,C++ Faq,我正试图在asio中保留一个已连接客户端的列表。我已经改编了docs()中的聊天服务器示例,下面是我最后得到的重要部分: #include <iostream> #include <boost/bind.hpp> #include <boost/shared_ptr.hpp> #include <boost/enable_shared_from_this.hpp> #include <boost/asio.hpp> #include &

我正试图在asio中保留一个已连接客户端的列表。我已经改编了docs()中的聊天服务器示例,下面是我最后得到的重要部分:

#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;
}
#包括
#包括
#包括
#包括
#包括
#包括
使用boost::asio::ip::tcp;
类tcp_连接;
std::设置客户端;
void add_客户端(boost::shared_ptr客户端)
{
客户。插入(客户);
}
类tcp\u连接:public boost::从\u启用\u共享\u
{
公众:
tcp_连接(boost::asio::io_服务和io_服务):套接字(io_服务)
{
}
tcp::socket-socket;
void start()
{
添加_客户端(来自_this()的共享_);
}
tcp::套接字和套接字()
{
返回插座;
}
};
类tcp_服务器
{
公众:
tcp_服务器(boost::asio::io_服务和io_服务)
:io_服务(io_服务),
接受者(io_服务,tcp::endpoint(tcp::v4(),6767))
{
tcp_连接*新建_连接=新建tcp_连接(io_服务);
acceptor\异步\u accept(新建\u连接->套接字(),
boost::bind(&tcp\u server::start\u accept,这个,新的\u连接,
boost::asio::占位符::错误);
}
私人:
无效启动\u接受(tcp\u连接*新\u连接,
常量boost::系统::错误(代码和错误)
{
如果(!错误)
{
新建连接->开始();
新建连接=新建tcp连接(io\U服务);
acceptor\异步\u accept(新建\u连接->套接字(),
boost::bind(&tcp\u server::start\u accept,这个,新的\u连接,
boost::asio::占位符::错误);
}
}
boost::asio::io_服务和io_服务;
tcp::接受者接受者;
};
int main()
{
尝试
{
boost::asio::io_服务io_服务;
tcp_服务器(io_服务);
io_service.run();
}
捕获(标准::异常&e)
{

std::cerr错误在于您在没有指向它的
共享\u ptr
的对象上使用
shared\u from\u this()
。这违反了
shared\u from\u this()
的先决条件,即必须至少已经创建了一个指向
共享\u ptr

问题的根本原因似乎是,您最初将
new
的结果存储在原始指针中。您应该将
new
的结果存储在智能指针中(通常,基本上)。也许您可以直接将智能指针存储在
客户机
列表中

我在评论中提到的另一种方法是完全停止使用
shared\u from\u 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);
}
if((boost::asio::error::eof==ec)| |(boost::asio::error::connection_reset==ec))
{
boost::shared_ptr牺牲品(这是boost::serialization::null_deleter());
删除(受害者);
}

也就是说,创建一个“哑”智能指针,它永远不会释放()但这将为您提供从客户机列表中删除它所需的内容。还有其他方法,例如使用比较函数搜索
std::set
,该函数使用一个
shared\u ptr
和一个原始指针,并知道如何比较它们指向的地址。选择哪种方式无关紧要,但你完全摆脱了这种情况。

约翰·兹温克的基本分析是:


错误在于您在没有指向它的共享\u ptr的对象上使用来自\u this()的共享\u。这违反了来自\u this()的共享\u的先决条件,即必须至少创建了一个指向它的共享\u ptr(并且仍然存在)

然而,他的建议似乎完全离题,在Asio代码中是危险的

您应该解决这个问题,实际上,首先不要处理指向
tcp\u连接的原始指针,而是始终使用
shared\u ptr

boost::bind
有一个很棒的特性,它绑定到
shared_ptr
很好,所以只要有异步操作,它就会自动保持指向对象的活动状态

在示例代码中,这意味着您不需要
客户机
向量,与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();
    }
}
典型输出:

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
//别忘了--v----公开继承:)
类tcp\u连接:public boost::从\u启用\u共享\u

为什么将
new
的结果存储在原始指针中,而只使用
shared\u from\u this()
稍后?似乎可以简化您的设计以完全消除此问题。
的boost文档从这个
中启用\u shared\u说
必须至少存在一个拥有t
的共享\u ptr实例p,而您似乎没有。@JonathanPotter我读过,但我不明白。@chrisvj我的解释是您需要有一个保存对象的
shared\u ptr
,然后才能使用
shared\u from\u this
。虽然我自己从未使用过它,但这只是一个猜测。只有在一个生命周期由共享指针管理的对象上调用
shared\u才有意义。否则,就不可能有一个共享指针其生命周期保证至少与对象的生命周期相同,并且此
共享的唯一目的就是返回这样的东西。因此,总之,您要求此
共享的做不可能的事。此
共享的情况是Boost Asio中会话管理的惯用用法。它的目的是简化异步会话的生存期管理。事实上,这里设置的
客户机
非常困难,因为几乎不可能在所有需要清理的地方都进行正确的清理
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
// Do not forget to ----v---- publicly inherit :)
class tcp_connection : public boost::enable_shared_from_this<tcp_connection>