在调用 std::bind 的产品后意外调用析构函数
Unexpected invocation of the destructor after an invokation of a product of std::bind
有一个使用 boost::asio::io_context
的简单示例
https://github.com/unegare/boost-ex/blob/500e46f4d3b41e2abe48e2deccfab39d44ae94e0/main.cpp
#include <boost/asio.hpp>
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <thread>
#include <vector>
#include <memory>
#include <mutex>
#include <chrono>
#include <iostream>
#include <exception>
std::mutex m_stdout;
class MyWorker {
std::shared_ptr<boost::asio::io_context> io_context;
std::shared_ptr<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> work_guard;
public:
MyWorker(std::shared_ptr<boost::asio::io_context> &_io_context, std::shared_ptr<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> &_work_guard):
io_context(_io_context), work_guard(_work_guard) {}
MyWorker(const MyWorker &mw): io_context(mw.io_context), work_guard(mw.work_guard) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] MyWorker copy constructor" << std::endl;
m_stdout.unlock();
}
MyWorker(MyWorker &&mw): io_context(std::move(mw.io_context)), work_guard(std::move(mw.work_guard)) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] MyWorker move constructor" << std::endl;
m_stdout.unlock();
}
~MyWorker() {}
void operator() () {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] Thread Start" << std::endl;
m_stdout.unlock();
while(true) {
try {
boost::system::error_code ec;
io_context->run(ec);
if (ec) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] MyWorker: received an error: " << ec << std::endl;
m_stdout.unlock();
continue;
}
break;
} catch (std::exception &ex) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] MyWorker: caught an exception: " << ex.what() << std::endl;
m_stdout.unlock();
}
}
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] Thread Finish" << std::endl;
m_stdout.unlock();
}
};
class Client: public std::enable_shared_from_this<Client> {
std::shared_ptr<boost::asio::io_context> io_context;
std::shared_ptr<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> work_guard;
std::shared_ptr<boost::asio::ip::tcp::socket> sock;
std::shared_ptr<std::array<char, 512>> buff;
public:
Client(std::shared_ptr<boost::asio::io_context> &_io_context, std::shared_ptr<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> &_work_guard, std::shared_ptr<boost::asio::ip::tcp::socket> &_sock):
io_context(_io_context), work_guard(_work_guard), sock(_sock) {
buff = std::make_shared<std::array<char,512>>();
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] " << __FUNCTION__ << " with args" << std::endl;
m_stdout.unlock();
}
Client(const Client &cl): io_context(cl.io_context), work_guard(cl.work_guard), sock(cl.sock), buff(cl.buff) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] " << __FUNCTION__ << " copy" << std::endl;
m_stdout.unlock();
}
Client(Client &&cl): io_context(std::move(cl.io_context)), work_guard(std::move(cl.work_guard)), sock(std::move(cl.sock)), buff(std::move(cl.buff)) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] " << __FUNCTION__ << " move" << std::endl;
m_stdout.unlock();
}
~Client() {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] " << __FUNCTION__ << " buff.use_count: " << buff.use_count() << " | sock.use_count: " << sock.use_count() << " | io_context.use_count: " << io_context.use_count() << std::endl;
m_stdout.unlock();
}
void OnConnect(const boost::system::error_code &ec) {
std::cout << __FUNCTION__ << std::endl;
if (ec) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] " << __FUNCTION__ << ": " << ec << std::endl;
m_stdout.unlock();
} else {
// buff = std::make_shared<std::array<char, 512>>();
char req[] = "GET / HTTP/1.1\r\nHost: unegare.info\r\n\r\n";
memcpy(buff->data(), req, strlen(req));
m_stdout.lock();
std::cout << req << std::endl;
m_stdout.unlock();
sock->async_write_some(boost::asio::buffer(buff->data(), strlen(buff->data())), std::bind(std::mem_fn(&Client::OnSend), this, std::placeholders::_1, std::placeholders::_2));
}
std::cout << __FUNCTION__ << " use_count: " << buff.use_count() << std::endl;
}
void OnSend(const boost::system::error_code &ec, std::size_t bytes_transferred) {
std::cout << __FUNCTION__ << " use_count: " << io_context.use_count() << std::endl;
if (ec) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] " << __FUNCTION__ << ": " << ec << std::endl;
m_stdout.unlock();
} else {
std::cout << __FUNCTION__ << " use_count: " << buff.use_count() << std::endl;
buff->fill(0);
std::cout << __FUNCTION__ << std::endl;
sock->async_read_some(boost::asio::buffer(buff->data(), buff->size()), std::bind(std::mem_fn(&Client::OnRecv), this, std::placeholders::_1, std::placeholders::_2));
}
}
void OnRecv(const boost::system::error_code &ec, std::size_t bytes_transferred) {
std::cout << __FUNCTION__ << std::endl;
if (ec) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] " << __FUNCTION__ << ": " << ec << std::endl;
m_stdout.unlock();
} else {
m_stdout.lock();
std::cout << buff->data() << std::endl;
m_stdout.unlock();
}
}
};
int main () {
std::shared_ptr<boost::asio::io_context> io_context(std::make_shared<boost::asio::io_context>());
std::shared_ptr<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> work_guard(
std::make_shared<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> (boost::asio::make_work_guard(*io_context))
);
MyWorker mw(io_context, work_guard);
std::vector<std::thread> vth;
vth.reserve(1);
for (int i = 1; i > 0; --i) {
vth.emplace_back(mw);
}
std::shared_ptr<Client> cl = 0;
try {
boost::asio::ip::tcp::resolver resolver(*io_context);
boost::asio::ip::tcp::resolver::query query("unegare.info", "80");
boost::asio::ip::tcp::endpoint ep = *resolver.resolve(query);
m_stdout.lock();
std::cout << "ep: " << ep << std::endl;
m_stdout.unlock();
std::shared_ptr<boost::asio::ip::tcp::socket> sock(std::make_shared<boost::asio::ip::tcp::socket>(*io_context));
std::shared_ptr<Client> cl2(std::make_shared<Client>(io_context, work_guard, sock));
cl = cl2->shared_from_this();
m_stdout.lock();
std::cout << "HERE: use_count = " << cl.use_count() << std::endl;
m_stdout.unlock();
sock->async_connect(ep, std::bind(std::mem_fn(&Client::OnConnect), *cl2->shared_from_this(), std::placeholders::_1));
std::this_thread::sleep_for(std::chrono::duration<double>(1));
m_stdout.lock();
std::cout << "AFTER CALL" << std::endl;
m_stdout.unlock();
// asm volatile ("");
} catch (std::exception &ex) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] Main Thread: caught an exception: " << ex.what() << std::endl;
m_stdout.unlock();
}
try {
char t;
std::cin >> t;
work_guard->reset();
// std::this_thread::sleep_for(std::chrono::duration<double>(1));
// std::cout << "Running" << std::endl;
// io_context->run();
} catch (std::exception &ex) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] Main Thread: caught an exception: " << ex.what() << std::endl;
m_stdout.unlock();
}
std::for_each(vth.begin(), vth.end(), std::mem_fn(&std::thread::join));
return 0;
}
标准输出:
[140487203505984] MyWorker copy constructor
[140487203505984] MyWorker move constructor
[140487185372928] Thread Start
ep: 95.165.130.37:80
[140487203505984] Client with args
HERE: use_count = 2
[140487203505984] Client copy
[140487203505984] Client move
[140487203505984] ~Client buff.use_count: 0 | sock.use_count: 0 | io_context.use_count: 0
[140487185372928] Client move
[140487185372928] ~Client buff.use_count: 0 | sock.use_count: 0 | io_context.use_count: 0
OnConnect
GET / HTTP/1.1
Host: unegare.info
OnConnect use_count: 2
[140487185372928] ~Client buff.use_count: 2 | sock.use_count: 3 | io_context.use_count: 5
Segmentation Fault (core dumped)
但是对Client
对象的错误引用导致的段错误的理解有点问题。
但是我不明白为什么cl2
在调用
之后就被破坏了
sock->async_connect(ep, std::bind(std::mem_fn(&Client::OnConnect), *cl2->shared_from_this(), std::placeholders::_1));
在第162行。
以及...为什么会调用复制构造函数?
从上面的 stdout
可以看出。
你没有跟踪 cl2
的破坏,你输出:cl2
是 std::shared_ptr<Client>
。您似乎在跟踪 Client
对象的构造。
你的问题是 cl2->make_shared_from_this()
前面的 *
:这将取消对 std::shared_ptr<Client>
的引用。 bind()
表达式看到一个 Client&
并通过复制捕获一个 Client
。删除 *
应该可以解决问题。我还没有完全理解代码正在尝试做什么(我正在 phone 上阅读它)但我猜你实际上想要捕获 std::shared_ptr<Client>
而不是 Client
.
此外,由于 cl2
已经是一个 std::shared_ptr<Client>
,所以在指向的对象上调用 make_shared_from_this()
没有意义。它只是重新创建了一个不必要的 st::shared_ptr<Client>
.
你试着去理解就好了。但是,从简单开始!
- 你的复制构造函数无法调用基础-class复制构造函数(!!)哎呀
- 你的绑定绑定到
this
(不会使共享指针保持活动状态)
*cl2->shared_from_this()
- 哎呀,这绑定到 *cl2
值 ¹ 的副本。这显然是 COPY 在你完成后被销毁的原因
无效引用由 1. 和 2.b.
的组合引起
我建议简化。很多!
- 使用零规则(仅在需要时声明特殊成员。在此,您引入了一个错误,因为您在手动编写不需要的复制构造函数时做错了)
- 在适当的地方使用非拥有引用(不是所有的东西都需要是智能指针)
不需要共享所有权的东西优先选择std::unique_ptr
(共享所有权应该很少见)
首选std::lock_guard
而不是手动锁定和解锁(这不是异常安全的)
- 其他很多:
work_guard
不需要复制,它已经有一个reset()
成员,通过const-reference捕获,如果你要捕获,不需要在 io_context::run
上使用 error_code
,在取消引用之前检查 resolve
的端点迭代器,改为使用 boost::asio::connect
,这样您就不必检查和迭代不同的端点等。
- 首选
std::string
而不是固定大小的缓冲区,如果您无论如何都在进行动态分配,请使用非实现定义的计时持续时间(例如 1.0s
,而不是 duration<double>(1)
),考虑使用 boost::thread_group
而不是 vector<std::thread>
或者至少只加入 joinable()
等 的线程
- 建议使
async_connect
成为 Client
的一部分,这样你就可以简单地绑定成员,就像所有其他绑定一样(使用 shared_from_this()
,而不是写你有的错误)
- 如果您还有时间,请考虑使用 Beast 进行 Http 请求和响应 creation/parsing
作为参考,MyWorker
可能是这样的:
class MyWorker {
ba::io_context& io_context;
public:
MyWorker(ba::io_context& _io_context) : io_context(_io_context) {}
//// best is to not mention these at all, because even `default`ing can change what compiler generates
//MyWorker(const MyWorker& mw) = default;
//MyWorker(MyWorker&& mw) = default;
//~MyWorker() = default;
void operator()() {
TRACE("Thread Start");
while (true) {
try {
io_context.run();
break; // normal end
} catch (boost::system::system_error const& se) {
TRACE("MyWorker: received an error: " << se.code().message());
} catch (std::exception const& ex) {
TRACE("MyWorker: caught an exception: " << ex.what());
}
}
TRACE("Thread Finish");
}
};
在这一点上,你完全可以把它变成一个 lambda:
auto mw = [&io_context] {
TRACE("Thread Start");
while (true) {
try {
io_context.run();
break; // normal end
} catch (boost::system::system_error const& se) {
TRACE("MyWorker: received an error: " << se.code().message());
} catch (std::exception const& ex) {
TRACE("MyWorker: caught an exception: " << ex.what());
}
}
TRACE("Thread Finish");
};
简单多了。
完整代码简化
看看例如新连接代码的简单性:
void Start() {
tcp::resolver resolver(io_context);
ba::async_connect(sock,
resolver.resolve({"unegare.info", "80"}),
[self=shared_from_this()](error_code ec, tcp::endpoint) { self->OnConnect(ec); });
}
调用 OnConnect
时打印端点。
#include <boost/asio.hpp>
#include <boost/thread.hpp>
#include <iomanip>
#include <iostream>
#include <memory>
#include <mutex>
namespace ba = boost::asio;
using ba::ip::tcp;
using namespace std::literals;
static std::mutex s_stdout;
#define TRACE(expr) { \
std::lock_guard<std::mutex> lk(s_stdout); \
std::cout << "[" << std::this_thread::get_id() << "] " << expr << std::endl; \
}
class Client : public std::enable_shared_from_this<Client> {
ba::io_context& io_context;
tcp::socket sock;
std::string buf;
public:
Client(ba::io_context& _io_context) : io_context(_io_context), sock{io_context}
{ }
void Start() {
tcp::resolver resolver(io_context);
ba::async_connect(sock,
resolver.resolve({"unegare.info", "80"}),
std::bind(std::mem_fn(&Client::OnConnect), shared_from_this(), std::placeholders::_1));
}
void OnConnect(const boost::system::error_code& ec) {
TRACE(__FUNCTION__ << " ep:" << sock.remote_endpoint());
if (ec) {
TRACE(__FUNCTION__ << ": " << ec.message());
} else {
buf = "GET / HTTP/1.1\r\nHost: unegare.info\r\n\r\n";
TRACE(std::quoted(buf));
sock.async_write_some(ba::buffer(buf),
std::bind(std::mem_fn(&Client::OnSend), shared_from_this(), std::placeholders::_1, std::placeholders::_2));
}
}
void OnSend(const boost::system::error_code& ec, std::size_t bytes_transferred) {
if (ec) {
TRACE(__FUNCTION__ << ": " << ec.message() << " and bytes_transferred: " << bytes_transferred);
} else {
TRACE(__FUNCTION__);
buf.assign(512, '[=13=]');
sock.async_read_some(ba::buffer(buf), std::bind(std::mem_fn(&Client::OnRecv), shared_from_this(), std::placeholders::_1, std::placeholders::_2));
}
}
void OnRecv(const boost::system::error_code& ec, std::size_t bytes_transferred) {
TRACE(__FUNCTION__);
if (ec) {
TRACE(__FUNCTION__ << ": " << ec.message() << " and bytes_transferred: " << bytes_transferred);
} else {
buf.resize(bytes_transferred);
TRACE(std::quoted(buf));
}
}
};
int main() {
ba::io_context io_context;
auto work_guard = make_work_guard(io_context);
boost::thread_group vth;
auto mw = [&io_context] {
TRACE("Thread Start");
while (true) {
try {
io_context.run();
break; // normal end
} catch (boost::system::system_error const& se) {
TRACE("MyWorker: received an error: " << se.code().message());
} catch (std::exception const& ex) {
TRACE("MyWorker: caught an exception: " << ex.what());
}
}
TRACE("Thread Finish");
};
vth.create_thread(mw);
try {
std::make_shared<Client>(io_context)->Start();
char t;
std::cin >> t;
work_guard.reset();
} catch (std::exception const& ex) {
TRACE("Main Thread: caught an exception: " << ex.what());
}
vth.join_all();
}
打印:
[140095938852608] Thread Start
[140095938852608] OnConnect ep:95.165.130.37:80
[140095938852608] "GET / HTTP/1.1
Host: unegare.info
"
[140095938852608] OnSend
[140095938852608] OnRecv
[140095938852608] "HTTP/1.1 200 OK
Date: Sun, 22 Dec 2019 22:26:56 GMT
Server: Apache/2.4.18 (Ubuntu)
Last-Modified: Sun, 10 Mar 2019 10:17:38 GMT
ETag: \"37-583bac3f3c843\"
Accept-Ranges: bytes
Content-Length: 55
Content-Type: text/html
<html>
<head>
</head>
<body>
It works.
</body>
</html>
"
q
[140095938852608] Thread Finish
奖金
std::bind
自 C++11 以来已过时,请考虑改用 lambda。既然Coliru不想再合作了,那我就post把三个变化的功能全部说出来:
void Start() {
tcp::resolver resolver(io_context);
ba::async_connect(sock,
resolver.resolve({"unegare.info", "80"}),
[self=shared_from_this()](error_code ec, tcp::endpoint) { self->OnConnect(ec); });
}
void OnConnect(const boost::system::error_code& ec) {
TRACE(__FUNCTION__ << " ep:" << sock.remote_endpoint());
if (ec) {
TRACE(__FUNCTION__ << ": " << ec.message());
} else {
buf = "GET / HTTP/1.1\r\nHost: unegare.info\r\n\r\n";
TRACE(std::quoted(buf));
sock.async_write_some(ba::buffer(buf),
[self=shared_from_this()](error_code ec, size_t bytes_transferred) { self->OnSend(ec, bytes_transferred); });
}
}
void OnSend(const boost::system::error_code& ec, std::size_t bytes_transferred) {
if (ec) {
TRACE(__FUNCTION__ << ": " << ec.message() << " and bytes_transferred: " << bytes_transferred);
} else {
TRACE(__FUNCTION__);
buf.assign(512, '[=15=]');
sock.async_read_some(ba::buffer(buf),
[self=shared_from_this()](error_code ec, size_t bytes_transferred) { self->OnRecv(ec, bytes_transferred); });
}
}
¹ Does boost::bind() copy parameters by reference or by value?
² Coliru 不允许网络访问
有一个使用 boost::asio::io_context
https://github.com/unegare/boost-ex/blob/500e46f4d3b41e2abe48e2deccfab39d44ae94e0/main.cpp
#include <boost/asio.hpp>
#include <boost/beast/core.hpp>
#include <boost/beast/http.hpp>
#include <boost/beast/version.hpp>
#include <thread>
#include <vector>
#include <memory>
#include <mutex>
#include <chrono>
#include <iostream>
#include <exception>
std::mutex m_stdout;
class MyWorker {
std::shared_ptr<boost::asio::io_context> io_context;
std::shared_ptr<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> work_guard;
public:
MyWorker(std::shared_ptr<boost::asio::io_context> &_io_context, std::shared_ptr<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> &_work_guard):
io_context(_io_context), work_guard(_work_guard) {}
MyWorker(const MyWorker &mw): io_context(mw.io_context), work_guard(mw.work_guard) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] MyWorker copy constructor" << std::endl;
m_stdout.unlock();
}
MyWorker(MyWorker &&mw): io_context(std::move(mw.io_context)), work_guard(std::move(mw.work_guard)) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] MyWorker move constructor" << std::endl;
m_stdout.unlock();
}
~MyWorker() {}
void operator() () {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] Thread Start" << std::endl;
m_stdout.unlock();
while(true) {
try {
boost::system::error_code ec;
io_context->run(ec);
if (ec) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] MyWorker: received an error: " << ec << std::endl;
m_stdout.unlock();
continue;
}
break;
} catch (std::exception &ex) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] MyWorker: caught an exception: " << ex.what() << std::endl;
m_stdout.unlock();
}
}
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] Thread Finish" << std::endl;
m_stdout.unlock();
}
};
class Client: public std::enable_shared_from_this<Client> {
std::shared_ptr<boost::asio::io_context> io_context;
std::shared_ptr<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> work_guard;
std::shared_ptr<boost::asio::ip::tcp::socket> sock;
std::shared_ptr<std::array<char, 512>> buff;
public:
Client(std::shared_ptr<boost::asio::io_context> &_io_context, std::shared_ptr<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> &_work_guard, std::shared_ptr<boost::asio::ip::tcp::socket> &_sock):
io_context(_io_context), work_guard(_work_guard), sock(_sock) {
buff = std::make_shared<std::array<char,512>>();
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] " << __FUNCTION__ << " with args" << std::endl;
m_stdout.unlock();
}
Client(const Client &cl): io_context(cl.io_context), work_guard(cl.work_guard), sock(cl.sock), buff(cl.buff) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] " << __FUNCTION__ << " copy" << std::endl;
m_stdout.unlock();
}
Client(Client &&cl): io_context(std::move(cl.io_context)), work_guard(std::move(cl.work_guard)), sock(std::move(cl.sock)), buff(std::move(cl.buff)) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] " << __FUNCTION__ << " move" << std::endl;
m_stdout.unlock();
}
~Client() {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] " << __FUNCTION__ << " buff.use_count: " << buff.use_count() << " | sock.use_count: " << sock.use_count() << " | io_context.use_count: " << io_context.use_count() << std::endl;
m_stdout.unlock();
}
void OnConnect(const boost::system::error_code &ec) {
std::cout << __FUNCTION__ << std::endl;
if (ec) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] " << __FUNCTION__ << ": " << ec << std::endl;
m_stdout.unlock();
} else {
// buff = std::make_shared<std::array<char, 512>>();
char req[] = "GET / HTTP/1.1\r\nHost: unegare.info\r\n\r\n";
memcpy(buff->data(), req, strlen(req));
m_stdout.lock();
std::cout << req << std::endl;
m_stdout.unlock();
sock->async_write_some(boost::asio::buffer(buff->data(), strlen(buff->data())), std::bind(std::mem_fn(&Client::OnSend), this, std::placeholders::_1, std::placeholders::_2));
}
std::cout << __FUNCTION__ << " use_count: " << buff.use_count() << std::endl;
}
void OnSend(const boost::system::error_code &ec, std::size_t bytes_transferred) {
std::cout << __FUNCTION__ << " use_count: " << io_context.use_count() << std::endl;
if (ec) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] " << __FUNCTION__ << ": " << ec << std::endl;
m_stdout.unlock();
} else {
std::cout << __FUNCTION__ << " use_count: " << buff.use_count() << std::endl;
buff->fill(0);
std::cout << __FUNCTION__ << std::endl;
sock->async_read_some(boost::asio::buffer(buff->data(), buff->size()), std::bind(std::mem_fn(&Client::OnRecv), this, std::placeholders::_1, std::placeholders::_2));
}
}
void OnRecv(const boost::system::error_code &ec, std::size_t bytes_transferred) {
std::cout << __FUNCTION__ << std::endl;
if (ec) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] " << __FUNCTION__ << ": " << ec << std::endl;
m_stdout.unlock();
} else {
m_stdout.lock();
std::cout << buff->data() << std::endl;
m_stdout.unlock();
}
}
};
int main () {
std::shared_ptr<boost::asio::io_context> io_context(std::make_shared<boost::asio::io_context>());
std::shared_ptr<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> work_guard(
std::make_shared<boost::asio::executor_work_guard<boost::asio::io_context::executor_type>> (boost::asio::make_work_guard(*io_context))
);
MyWorker mw(io_context, work_guard);
std::vector<std::thread> vth;
vth.reserve(1);
for (int i = 1; i > 0; --i) {
vth.emplace_back(mw);
}
std::shared_ptr<Client> cl = 0;
try {
boost::asio::ip::tcp::resolver resolver(*io_context);
boost::asio::ip::tcp::resolver::query query("unegare.info", "80");
boost::asio::ip::tcp::endpoint ep = *resolver.resolve(query);
m_stdout.lock();
std::cout << "ep: " << ep << std::endl;
m_stdout.unlock();
std::shared_ptr<boost::asio::ip::tcp::socket> sock(std::make_shared<boost::asio::ip::tcp::socket>(*io_context));
std::shared_ptr<Client> cl2(std::make_shared<Client>(io_context, work_guard, sock));
cl = cl2->shared_from_this();
m_stdout.lock();
std::cout << "HERE: use_count = " << cl.use_count() << std::endl;
m_stdout.unlock();
sock->async_connect(ep, std::bind(std::mem_fn(&Client::OnConnect), *cl2->shared_from_this(), std::placeholders::_1));
std::this_thread::sleep_for(std::chrono::duration<double>(1));
m_stdout.lock();
std::cout << "AFTER CALL" << std::endl;
m_stdout.unlock();
// asm volatile ("");
} catch (std::exception &ex) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] Main Thread: caught an exception: " << ex.what() << std::endl;
m_stdout.unlock();
}
try {
char t;
std::cin >> t;
work_guard->reset();
// std::this_thread::sleep_for(std::chrono::duration<double>(1));
// std::cout << "Running" << std::endl;
// io_context->run();
} catch (std::exception &ex) {
m_stdout.lock();
std::cout << "[" << std::this_thread::get_id() << "] Main Thread: caught an exception: " << ex.what() << std::endl;
m_stdout.unlock();
}
std::for_each(vth.begin(), vth.end(), std::mem_fn(&std::thread::join));
return 0;
}
标准输出:
[140487203505984] MyWorker copy constructor
[140487203505984] MyWorker move constructor
[140487185372928] Thread Start
ep: 95.165.130.37:80
[140487203505984] Client with args
HERE: use_count = 2
[140487203505984] Client copy
[140487203505984] Client move
[140487203505984] ~Client buff.use_count: 0 | sock.use_count: 0 | io_context.use_count: 0
[140487185372928] Client move
[140487185372928] ~Client buff.use_count: 0 | sock.use_count: 0 | io_context.use_count: 0
OnConnect
GET / HTTP/1.1
Host: unegare.info
OnConnect use_count: 2
[140487185372928] ~Client buff.use_count: 2 | sock.use_count: 3 | io_context.use_count: 5
Segmentation Fault (core dumped)
但是对Client
对象的错误引用导致的段错误的理解有点问题。
但是我不明白为什么cl2
在调用
sock->async_connect(ep, std::bind(std::mem_fn(&Client::OnConnect), *cl2->shared_from_this(), std::placeholders::_1));
在第162行。
以及...为什么会调用复制构造函数?
从上面的 stdout
可以看出。
你没有跟踪 cl2
的破坏,你输出:cl2
是 std::shared_ptr<Client>
。您似乎在跟踪 Client
对象的构造。
你的问题是 cl2->make_shared_from_this()
前面的 *
:这将取消对 std::shared_ptr<Client>
的引用。 bind()
表达式看到一个 Client&
并通过复制捕获一个 Client
。删除 *
应该可以解决问题。我还没有完全理解代码正在尝试做什么(我正在 phone 上阅读它)但我猜你实际上想要捕获 std::shared_ptr<Client>
而不是 Client
.
此外,由于 cl2
已经是一个 std::shared_ptr<Client>
,所以在指向的对象上调用 make_shared_from_this()
没有意义。它只是重新创建了一个不必要的 st::shared_ptr<Client>
.
你试着去理解就好了。但是,从简单开始!
- 你的复制构造函数无法调用基础-class复制构造函数(!!)哎呀
- 你的绑定绑定到
this
(不会使共享指针保持活动状态)*cl2->shared_from_this()
- 哎呀,这绑定到*cl2
值 ¹ 的副本。这显然是 COPY 在你完成后被销毁的原因
无效引用由 1. 和 2.b.
的组合引起我建议简化。很多!
- 使用零规则(仅在需要时声明特殊成员。在此,您引入了一个错误,因为您在手动编写不需要的复制构造函数时做错了)
- 在适当的地方使用非拥有引用(不是所有的东西都需要是智能指针)
不需要共享所有权的东西优先选择
std::unique_ptr
(共享所有权应该很少见)首选
std::lock_guard
而不是手动锁定和解锁(这不是异常安全的)- 其他很多:
work_guard
不需要复制,它已经有一个reset()
成员,通过const-reference捕获,如果你要捕获,不需要在io_context::run
上使用error_code
,在取消引用之前检查resolve
的端点迭代器,改为使用boost::asio::connect
,这样您就不必检查和迭代不同的端点等。 - 首选
std::string
而不是固定大小的缓冲区,如果您无论如何都在进行动态分配,请使用非实现定义的计时持续时间(例如1.0s
,而不是duration<double>(1)
),考虑使用boost::thread_group
而不是vector<std::thread>
或者至少只加入joinable()
等 的线程
- 建议使
async_connect
成为Client
的一部分,这样你就可以简单地绑定成员,就像所有其他绑定一样(使用shared_from_this()
,而不是写你有的错误) - 如果您还有时间,请考虑使用 Beast 进行 Http 请求和响应 creation/parsing
作为参考,MyWorker
可能是这样的:
class MyWorker {
ba::io_context& io_context;
public:
MyWorker(ba::io_context& _io_context) : io_context(_io_context) {}
//// best is to not mention these at all, because even `default`ing can change what compiler generates
//MyWorker(const MyWorker& mw) = default;
//MyWorker(MyWorker&& mw) = default;
//~MyWorker() = default;
void operator()() {
TRACE("Thread Start");
while (true) {
try {
io_context.run();
break; // normal end
} catch (boost::system::system_error const& se) {
TRACE("MyWorker: received an error: " << se.code().message());
} catch (std::exception const& ex) {
TRACE("MyWorker: caught an exception: " << ex.what());
}
}
TRACE("Thread Finish");
}
};
在这一点上,你完全可以把它变成一个 lambda:
auto mw = [&io_context] {
TRACE("Thread Start");
while (true) {
try {
io_context.run();
break; // normal end
} catch (boost::system::system_error const& se) {
TRACE("MyWorker: received an error: " << se.code().message());
} catch (std::exception const& ex) {
TRACE("MyWorker: caught an exception: " << ex.what());
}
}
TRACE("Thread Finish");
};
简单多了。
完整代码简化
看看例如新连接代码的简单性:
void Start() {
tcp::resolver resolver(io_context);
ba::async_connect(sock,
resolver.resolve({"unegare.info", "80"}),
[self=shared_from_this()](error_code ec, tcp::endpoint) { self->OnConnect(ec); });
}
调用 OnConnect
时打印端点。
#include <boost/asio.hpp>
#include <boost/thread.hpp>
#include <iomanip>
#include <iostream>
#include <memory>
#include <mutex>
namespace ba = boost::asio;
using ba::ip::tcp;
using namespace std::literals;
static std::mutex s_stdout;
#define TRACE(expr) { \
std::lock_guard<std::mutex> lk(s_stdout); \
std::cout << "[" << std::this_thread::get_id() << "] " << expr << std::endl; \
}
class Client : public std::enable_shared_from_this<Client> {
ba::io_context& io_context;
tcp::socket sock;
std::string buf;
public:
Client(ba::io_context& _io_context) : io_context(_io_context), sock{io_context}
{ }
void Start() {
tcp::resolver resolver(io_context);
ba::async_connect(sock,
resolver.resolve({"unegare.info", "80"}),
std::bind(std::mem_fn(&Client::OnConnect), shared_from_this(), std::placeholders::_1));
}
void OnConnect(const boost::system::error_code& ec) {
TRACE(__FUNCTION__ << " ep:" << sock.remote_endpoint());
if (ec) {
TRACE(__FUNCTION__ << ": " << ec.message());
} else {
buf = "GET / HTTP/1.1\r\nHost: unegare.info\r\n\r\n";
TRACE(std::quoted(buf));
sock.async_write_some(ba::buffer(buf),
std::bind(std::mem_fn(&Client::OnSend), shared_from_this(), std::placeholders::_1, std::placeholders::_2));
}
}
void OnSend(const boost::system::error_code& ec, std::size_t bytes_transferred) {
if (ec) {
TRACE(__FUNCTION__ << ": " << ec.message() << " and bytes_transferred: " << bytes_transferred);
} else {
TRACE(__FUNCTION__);
buf.assign(512, '[=13=]');
sock.async_read_some(ba::buffer(buf), std::bind(std::mem_fn(&Client::OnRecv), shared_from_this(), std::placeholders::_1, std::placeholders::_2));
}
}
void OnRecv(const boost::system::error_code& ec, std::size_t bytes_transferred) {
TRACE(__FUNCTION__);
if (ec) {
TRACE(__FUNCTION__ << ": " << ec.message() << " and bytes_transferred: " << bytes_transferred);
} else {
buf.resize(bytes_transferred);
TRACE(std::quoted(buf));
}
}
};
int main() {
ba::io_context io_context;
auto work_guard = make_work_guard(io_context);
boost::thread_group vth;
auto mw = [&io_context] {
TRACE("Thread Start");
while (true) {
try {
io_context.run();
break; // normal end
} catch (boost::system::system_error const& se) {
TRACE("MyWorker: received an error: " << se.code().message());
} catch (std::exception const& ex) {
TRACE("MyWorker: caught an exception: " << ex.what());
}
}
TRACE("Thread Finish");
};
vth.create_thread(mw);
try {
std::make_shared<Client>(io_context)->Start();
char t;
std::cin >> t;
work_guard.reset();
} catch (std::exception const& ex) {
TRACE("Main Thread: caught an exception: " << ex.what());
}
vth.join_all();
}
打印:
[140095938852608] Thread Start
[140095938852608] OnConnect ep:95.165.130.37:80
[140095938852608] "GET / HTTP/1.1
Host: unegare.info
"
[140095938852608] OnSend
[140095938852608] OnRecv
[140095938852608] "HTTP/1.1 200 OK
Date: Sun, 22 Dec 2019 22:26:56 GMT
Server: Apache/2.4.18 (Ubuntu)
Last-Modified: Sun, 10 Mar 2019 10:17:38 GMT
ETag: \"37-583bac3f3c843\"
Accept-Ranges: bytes
Content-Length: 55
Content-Type: text/html
<html>
<head>
</head>
<body>
It works.
</body>
</html>
"
q
[140095938852608] Thread Finish
奖金
std::bind
自 C++11 以来已过时,请考虑改用 lambda。既然Coliru不想再合作了,那我就post把三个变化的功能全部说出来:
void Start() {
tcp::resolver resolver(io_context);
ba::async_connect(sock,
resolver.resolve({"unegare.info", "80"}),
[self=shared_from_this()](error_code ec, tcp::endpoint) { self->OnConnect(ec); });
}
void OnConnect(const boost::system::error_code& ec) {
TRACE(__FUNCTION__ << " ep:" << sock.remote_endpoint());
if (ec) {
TRACE(__FUNCTION__ << ": " << ec.message());
} else {
buf = "GET / HTTP/1.1\r\nHost: unegare.info\r\n\r\n";
TRACE(std::quoted(buf));
sock.async_write_some(ba::buffer(buf),
[self=shared_from_this()](error_code ec, size_t bytes_transferred) { self->OnSend(ec, bytes_transferred); });
}
}
void OnSend(const boost::system::error_code& ec, std::size_t bytes_transferred) {
if (ec) {
TRACE(__FUNCTION__ << ": " << ec.message() << " and bytes_transferred: " << bytes_transferred);
} else {
TRACE(__FUNCTION__);
buf.assign(512, '[=15=]');
sock.async_read_some(ba::buffer(buf),
[self=shared_from_this()](error_code ec, size_t bytes_transferred) { self->OnRecv(ec, bytes_transferred); });
}
}
¹ Does boost::bind() copy parameters by reference or by value?
² Coliru 不允许网络访问