提问人:chrisvj 提问时间:12/30/2014 最后编辑:Alexis Wilkechrisvj 更新时间:4/22/2022 访问量:53870
shared_from_this导致bad_weak_ptr
shared_from_this causing 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()
异常:tr1::bad_weak_ptr
我做了一些搜索,它看起来很特别,但我似乎找不到我需要更改的确切内容。shared_from_this()
答:
错误是你正在一个没有指向它的对象上使用。这违反了 的前提条件,即必须至少已经创建(并且仍然存在)指向 。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);
}
也就是说,创建一个“哑”智能指针,该指针永远不会释放 (https://stackoverflow.com/a/5233034/4323),但它会为您提供将其从客户端列表中删除所需的内容。还有其他方法可以做到这一点,例如使用比较函数进行搜索,该函数采用一个和一个原始指针,并知道比较它们指向的地址。你选择哪种方式并不重要,但你完全摆脱了这种情况。std::set
shared_ptr
shared_from_this()
评论
shared_from_this
clients
约翰·兹温克(John Zwinck)的基本分析是正确的:
错误是你在一个没有shared_ptr指向它的对象上使用 shared_from_this()。这违反了 shared_from_this() 的前提条件,即必须至少已经创建(并且仍然存在)指向此shared_ptr。
然而,他的建议在 Asio 代码中似乎完全偏离了重点和危险。
你应该解决这个问题 - 事实上 - 首先不要处理原始指针,而是始终使用。tcp_connection
shared_ptr
boost::bind
具有很棒的功能,它可以很好地绑定到它,因此只要对它进行一些异步操作,它就会自动保持指向对象的活动状态。shared_ptr<>
在您的示例代码中,这意味着您不需要向量,这与 John 的答案相反: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();
}
}
我提供了一个示例,可以做一些琐碎的工作(它每秒循环向客户端写入“hello world”,直到客户端断开连接。当它出现时,您可以看到正在运行的操作的析构函数:tcp_connection
tcp_connection
#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
评论
weak_ptr
public
// Do not forget to ----v---- publicly inherit :)
class tcp_connection : public boost::enable_shared_from_this<tcp_connection>
评论
shared_from_this()
这里的答案很棒,并揭示了我问题的解决方案。然而
- 我搜索了“bad_weak_ptr shared_from_this”,这是最好的结果,尽管我没有提到提升。我使用的是标准的 C++17。
- OP就是一个具体的例子;有点复杂,你必须挖掘一些不相关的 boost 套接字代码才能找到核心问题。
鉴于这两点,我认为发布根本问题的 MRE 以及解决它所需的内容可能会有所帮助。此代码源自 cppreference:
有问题的代码
#include <iostream>
#include <memory>
struct Foo : public std::enable_shared_from_this<Foo> {
Foo() { std::cout << "Foo::Foo\n"; }
~Foo() { std::cout << "Foo::~Foo\n"; }
std::shared_ptr<Foo> getFoo() { return shared_from_this(); }
};
int main()
{
try
{
Foo *f = new Foo;
// Oops! this throws std::bad_weak_ptr. f is a raw pointer to Foo (not
// managed by a shared pointer), and calling getFoo tries to create a
// shared_ptr from the internal weak_ptr. The internal weak_ptr is nullptr,
// so this cannot be done, and the exception is thrown. Note, the
// cppreference link above says that prior to C++17, trying to do this
// is undefined behavior. However, in my testing on godbolt, an exception
// is thrown for C++11, 14, and 17 with gcc.
std::shared_ptr<Foo> sp = f->getFoo();
}
catch(const std::bad_weak_ptr& bwp)
{
// the exception is caught, and "bad_weak_ptr" is printed to stdout
std::cout << bwp.what();
exit(-1);
}
return 0;
}
输出:
Foo::Foo
bad_weak_ptr
可能的修复
确保由shared_ptr管理:f
try
{
Foo *f = new Foo;
// this time, introduce a shared pointer
std::shared_ptr<Foo> sp(f);
// now, f is managed by a shared pointer. Its internal weak_ptr is valid,
// and so the retrieval of a shared_ptr via shared_from_this works as
// desired. We can get a weak_ptr or a shared_ptr
std::weak_ptr<Foo> wp = f->getFoo();
std::shared_ptr<Foo> sp2 = f->getFoo();
// all pointers go out of scope and the Foo object is deleted once
// the reference count reaches 0
}
catch(const std::bad_weak_ptr& bwp)
{
std::cout << bwp.what();
exit(-1);
}
输出:
Foo::Foo
Foo::~Foo
如其他答案所述,如上所示,在调用之前,必须有一个由共享指针管理的对象,否则将得到异常或 UB,具体取决于您的 C++ 标准。这里有一个游乐场,适合任何感兴趣的人。shared_from_this
bad_weak_ptr
上一个:C++17 有哪些新功能?
评论
new
shared_from_this()
enable_shared_from_this
There must exist at least one shared_ptr instance p that owns t
shared_ptr
shared_from_this
shared_from_this
shared_from_this
shared_from_this