boost shared_ptr的小技巧

 




这段代码有两个奇妙之处:

1. A的析构函数是protected,编译时并没有错误提示

2. A的析构函数不是virtual的,但最后B的析构函数却被调用了


shared_ptr真是让人眼前一亮,那就到boost的里面来探一探究竟吧


先从shared_ptr的申明开始:


 


注意到这里有两个变量, px, pn,来看看它们是怎么被申明的吧:


 


px与pn都是用p来做初始化的,px的类型就是模块参数T的指针, 但pn的类型还要再看一看:


 


而这个pi的定义是:


sp_counted_base * pi_;


从sp_counted_base类中可以看出,这就是引用计数的具体实现,对我们这里的问题没有任何意义,不过pi实际指向的对象sp_counted_impl_p却有些意思:


 


其中的px_申明为:


X * px_;



好了,把上面这结连起来看,问题就很清楚了。


在创建shared_ptr<X>对象时,内部实际上初始化了两个指针,一个是shared_ptr里的px,其值就是原始指针p,另一个是pn,在shared_count中可以看出来,其类型是p的实际类型,也就是说,p的实际类型与shared_ptr的模板参数类型X可以不一样,当然也只能是派生关系,否则也编译不过去。


放到最开始的例子中就是,px类型为A, pn内部类型为B。


其实在shared_ptr的构造函数中有这样一段注释:“Y must be complete”,也就是说原始指针的类型必须是完整的,因为这时编译器可以检查出指针的真正类型,并且在pn中记录下该类型,然后在释放的时候就可以调用正确的析构函数了。


但是如果在这里指针的类型就已经丢失了,那结果自然是不正确的,比如我们可以这样试一下:


 


当然,直接这样编译是通不过的,编译器会提示你A的析构函数是protected,对象无法被删除。是的,这就是问题所在了,此时传递给shared_ptr的参数类型已经是A了,pn中记录的指针类型也是A,所以他在删除该指针对象的时候也就只会调用A的析构函数,这时自然是通不过编译检查的。



然后再来看一看boost为什么要这样做,一个指针保存了两份,当然是有他的原因的。仔细看一下会发现,在对指针进行操作时使用的是shared_ptr内保存的px对象,而在删除指针时用的是pn内保存的指针。这样就很明白了,用模板参数类型的指针,一般也就是基类指针来调用方法,可以很好的支持多态,而使用原始类型的指针来做删除操作,这样又能保证对象被安全的删除。


Boost果然非常神奇。

 

namespace beast = boost::beast; // from <boost/beast.hpp> namespace http = beast::http; // from <boost/beast/http.hpp> namespace net = boost::asio; // from <boost/asio.hpp> using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp> using TypeCallbackClientOnDone = std::function<void(const char*, const std::string&, const char*)>; // Performs an HTTP GET and prints the response class ClientSession : public std::enable_shared_from_this<ClientSession> { public: // Objects are constructed with a strand to // ensure that handlers do not execute concurrently. explicit ClientSession(net::io_context& ioc, std::shared_ptr<beast::tcp_stream> stream, const std::string& host, int port); // Start the asynchronous operation void Run(int timeout, char const* target, const char* contentType, const std::string& content, const TypeCallbackClientOnDone& cb); // 拿走其内的Stream std::shared_ptr<beast::tcp_stream> TakeStream(); private: void DoRun(int timeout, const std::string& target, const std::string& contentType, const std::string& content, const TypeCallbackClientOnDone& cb); void OnConnect(beast::error_code* ecPtr, int timeout); void OnWrite(beast::error_code ec, std::size_t bytes_transferred, int timeout); void OnRead(beast::error_code ec, std::size_t bytes_transferred, int timeout); void OnFail(const std::string& errMsg, char const* what); void ReportFail(const std::string& errMsg, char const* what); void CloseSocketAndConnect(int timeout); private: net::io_context& ioc_; std::shared_ptr<beast::tcp_stream> stream_; std::string host_; int port_{ 0 }; //用于底层连接复用,是否失败时重连 bool reconnect_{ true }; beast::flat_buffer buffer_; // (Must persist between reads) http::request<http::string_body> request_; http::response<http::string_body> res_; TypeCallbackClientOnDone funcOnDone_; }; 改用strand序列化
最新发布
07-24
<think>为了将`ClientSession`改为使用`strand`序列化所有异步操作,我们需要确保所有异步操作都通过`strand`分发执行。这样,即使`io_context`在多线程环境下运行,同一个`ClientSession`实例的异步操作也不会并发执行,从而保证线程安全。 ### 修改步骤: 1. **添加strand成员**:在`ClientSession`类中添加一个`net::strand`成员,用于序列化所有异步操作。 2. **修改构造函数**:初始化`strand_`成员。 3. **修改所有异步操作**:确保每个异步操作的开始(包括回调)都通过`strand_`分发。可以使用`net::dispatch`(立即执行)或`net::post`(排队执行)来将操作提交到`strand`中。另外,在异步操作的完成处理函数中,使用`bind_executor`绑定到`strand_`,确保回调在`strand_`中执行。 4. **调整`Run`方法**:将实际执行操作的`DoRun`方法通过`strand_`分发。 5. **调整`TakeStream`方法**:由于`TakeStream`可能被外部调用,我们也需要确保它在`strand`中执行,或者考虑其线程安全性(这里可能不需要,因为通常是在操作完成后调用,但为了安全,我们可以加锁或通过`strand`分发)。不过,根据需求,如果外部调用者能保证在正确的时机调用,可以暂时不修改,但为了线程安全,最好也通过`strand`分发。 ### 代码修改: ```cpp #include <boost/beast/core.hpp> #include <boost/beast/http.hpp> #include <boost/asio.hpp> namespace beast = boost::beast; namespace http = beast::http; namespace net = boost::asio; using tcp = net::ip::tcp; using TypeCallbackClientOnDone = std::function<void(const char*, const std::string&, const char*)>; class ClientSession : public std::enable_shared_from_this<ClientSession> { public: explicit ClientSession(net::io_context& ioc, std::shared_ptr<beast::tcp_stream> stream, const std::string& host, int port) : ioc_(ioc) , strand_(net::make_strand(ioc)) // 创建strand , stream_(stream) , host_(host) , port_(port) { } void Run(int timeout, char const* target, const char* contentType, const std::string& content, const TypeCallbackClientOnDone& cb) { // 通过strand分发执行,确保线程安全 net::post(strand_, [self = shared_from_this(), timeout, target = std::string(target), contentType = std::string(contentType), content, cb]() { self->DoRun(timeout, target, contentType, content, cb); }); } // 注意:TakeStream可能需要考虑线程安全,如果外部调用者不确定,可以也通过strand分发 std::shared_ptr<beast::tcp_stream> TakeStream() { // 由于TakeStream可能被外部调用,我们通过strand确保安全 // 使用promise和future来同步获取结果 auto promise = std::make_shared<std::promise<std::shared_ptr<beast::tcp_stream>>>(); auto future = promise->get_future(); net::post(strand_, [self = shared_from_this(), promise]() { promise->set_value(self->stream_); // 注意:TakeStream后,当前session不再持有stream self->stream_.reset(); }); return future.get(); } private: void DoRun(int timeout, const std::string& target, const std::string& contentType, const std::string& content, const TypeCallbackClientOnDone& cb) { // 保存回调 funcOnDone_ = cb; // 设置请求 request_.version(11); // HTTP/1.1 request_.method(http::verb::post); request_.target(target); request_.set(http::field::host, host_); request_.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING); request_.set(http::field::content_type, contentType); request_.body() = content; request_.prepare_payload(); // 设置超时 stream_->expires_after(std::chrono::milliseconds(timeout)); // 如果stream已经连接,直接进行写操作;否则需要先连接 if (stream_->socket().is_open()) { DoWrite(timeout); } else { // 异步连接 auto const address = net::ip::make_address(host_); stream_->async_connect( tcp::endpoint{ address, static_cast<unsigned short>(port_) }, beast::bind_front_handler( &ClientSession::OnConnect, shared_from_this(), timeout ) ); } } void OnConnect(beast::error_code ec, int timeout) { if (ec) { return ReportFail(ec.message(), "connect"); } DoWrite(timeout); } void DoWrite(int timeout) { // 发送HTTP请求 http::async_write( *stream_, request_, beast::bind_front_handler( &ClientSession::OnWrite, shared_from_this(), timeout ) ); } void OnWrite(beast::error_code ec, std::size_t, int timeout) { if (ec) { return ReportFail(ec.message(), "write"); } // 读取响应 http::async_read( *stream_, buffer_, res_, beast::bind_front_handler( &ClientSession::OnRead, shared_from_this(), timeout ) ); } void OnRead(beast::error_code ec, std::size_t, int timeout) { if (ec) { return ReportFail(ec.message(), "read"); } // 调用回调,传递响应结果 if (funcOnDone_) { funcOnDone_(res_.body().c_str(), "", nullptr); } // 注意:这里不关闭socket,以便复用 // 重置buffer和响应对象,以便下次使用 buffer_.consume(buffer_.size()); res_ = {}; } void ReportFail(const std::string& errMsg, char const* what) { if (funcOnDone_) { funcOnDone_(nullptr, errMsg, what); } // 失败后关闭socket CloseSocket(); } void CloseSocket() { beast::error_code ec; stream_->socket().shutdown(tcp::socket::shutdown_both, ec); stream_->close(); buffer_.consume(buffer_.size()); } private: net::io_context& ioc_; net::strand<net::io_context::executor_type> strand_; // 添加strand成员 std::shared_ptr<beast::tcp_stream> stream_; std::string host_; int port_; beast::flat_buffer buffer_; http::request<http::string_body> request_; http::response<http::string_body> res_; TypeCallbackClientOnDone funcOnDone_; }; ``` ### 关键点解释: 1. **添加strand**:使用`net::make_strand(ioc)`创建strand,它关联到`io_context`的执行器。 2. **通过strand分发操作**:所有公共接口(如`Run`和`TakeStream`)都通过`net::post`将实际工作分发到`strand`中,确保操作在`strand`中顺序执行。 3. **异步操作回调绑定到strand**:在发起异步操作(如`async_connect`、`async_write`、`async_read`)时,使用`beast::bind_front_handler`绑定成员函数,这些回调函数会在异步操作完成时被调用。由于我们在`strand`中启动这些操作,并且这些操作的回调也是通过`strand`分发(因为我们在`strand`中启动操作,所以回调默认会在同一个`strand`中执行),因此保证了整个操作链都在同一个`strand`中执行,不会并发。 4. **TakeStream的线程安全**:在`TakeStream`中,我们通过`net::post`将获取`stream_`的操作放到`strand`中,并使用`promise`和`future`来等待结果。这样外部调用`TakeStream`的线程会阻塞直到`strand`中执行完该操作,从而保证线程安全。 ### 注意事项: - 使用`strand`后,同一个`ClientSession`实例的所有异步操作(包括回调)都会在同一个`strand`中顺序执行,因此不会出现并发访问成员变量的情况。 - 在`TakeStream`中,我们使用了阻塞等待(`future.get()`),这可能会导致调用线程阻塞。如果不想阻塞,可以设计一个异步版本的`TakeStream`,通过回调返回结果。 - 在`Run`方法中,我们将参数复制到lambda中,确保在分发到`strand`时参数仍然有效。 ### 相关问题: §§相关问题§§: 1. 在Boost.Asio中,strand和io_context的关系是什么? 2. 使用strand时,如何避免死锁或竞争条件? 3. 在异步操作链中,如何确保对象的生命周期? 4. Boost.Asio中的`bind_executor`和`bind_front_handler`有什么区别和联系?</think>### 使用 Strand 序列化改造 ClientSession 以下是使用 `boost::asio::strand` 重构 `ClientSession` 的完整实现,确保所有异步操作线程安全: ```cpp #include <boost/beast/core.hpp> #include <boost/beast/http.hpp> #include <boost/asio.hpp> #include <memory> namespace beast = boost::beast; namespace http = beast::http; namespace net = boost::asio; using tcp = net::ip::tcp; using TypeCallbackClientOnDone = std::function<void(const char*, const std::string&, const char*)>; class ClientSession : public std::enable_shared_from_this<ClientSession> { public: explicit ClientSession(net::io_context& ioc, std::shared_ptr<beast::tcp_stream> stream, const std::string& host, int port) : ioc_(ioc) , strand_(net::make_strand(ioc)) // 关键:创建strand , stream_(std::move(stream)) , host_(host) , port_(port) {} void Run(int timeout, char const* target, const char* contentType, const std::string& content, const TypeCallbackClientOnDone& cb) { // 通过strand分发操作 net::post( strand_, [self = shared_from_this(), timeout, target = std::string(target), contentType = std::string(contentType), content, cb]() { self->DoRun(timeout, target, contentType, content, cb); } ); } std::shared_ptr<beast::tcp_stream> TakeStream() { // 使用promise/future确保线程安全 auto promise = std::make_shared<std::promise<std::shared_ptr<beast::tcp_stream>>>(); auto future = promise->get_future(); net::post( strand_, [self = shared_from_this(), promise]() { promise->set_value(self->stream_); self->stream_.reset(); // 转移所有权后重置 } ); return future.get(); } private: void DoRun(int timeout, const std::string& target, const std::string& contentType, const std::string& content, const TypeCallbackClientOnDone& cb) { funcOnDone_ = cb; request_.version(11); // HTTP/1.1 request_.method(http::verb::post); request_.target(target); request_.set(http::field::host, host_); request_.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING); request_.set(http::field::content_type, contentType); request_.body() = content; request_.prepare_payload(); // 设置超时 stream_->expires_after(std::chrono::milliseconds(timeout)); if (stream_->socket().is_open()) { DoWrite(timeout); } else { auto const address = net::ip::make_address(host_); // 异步连接(绑定到strand) stream_->async_connect( tcp::endpoint{address, static_cast<unsigned short>(port_)}, net::bind_executor( strand_, beast::bind_front_handler( &ClientSession::OnConnect, shared_from_this(), timeout ) ) ); } } void OnConnect(beast::error_code ec, int timeout) { if (ec) { return ReportFail(ec.message(), "connect"); } DoWrite(timeout); } void DoWrite(int timeout) { // 使用strand绑定写操作 http::async_write( *stream_, request_, net::bind_executor( strand_, beast::bind_front_handler( &ClientSession::OnWrite, shared_from_this(), timeout ) ) ); } void OnWrite(beast::error_code ec, std::size_t, int timeout) { if (ec) { return ReportFail(ec.message(), "write"); } // 使用strand绑定读操作 http::async_read( *stream_, buffer_, res_, net::bind_executor( strand_, beast::bind_front_handler( &ClientSession::OnRead, shared_from_this(), timeout ) ) ); } void OnRead(beast::error_code ec, std::size_t, int timeout) { if (ec) { return ReportFail(ec.message(), "read"); } // 在strand中执行回调 if (funcOnDone_) { funcOnDone_(res_.body().c_str(), "", nullptr); } // 清理状态 buffer_.consume(buffer_.size()); res_ = {}; } void ReportFail(const std::string& errMsg, char const* what) { // 确保回调在strand中执行 net::post( strand_, [self = shared_from_this(), errMsg, what]() { if (self->funcOnDone_) { self->funcOnDone_(nullptr, errMsg, what); } self->CloseSocket(); } ); } void CloseSocket() { beast::error_code ec; stream_->cancel(); stream_->socket().shutdown(tcp::socket::shutdown_both, ec); stream_->close(ec); buffer_.consume(buffer_.size()); } private: net::io_context& ioc_; net::strand<net::io_context::executor_type> strand_; // 核心同步机制 std::shared_ptr<beast::tcp_stream> stream_; std::string host_; int port_; beast::flat_buffer buffer_; http::request<http::string_body> request_; http::response<http::string_body> res_; TypeCallbackClientOnDone funcOnDone_; }; ``` ### 关键改造点详解 1. **Strand 集成** ```cpp net::strand<net::io_context::executor_type> strand_; ``` - 使用 `net::make_strand(ioc)` 创建与 io_context 关联的 strand - 所有操作通过 `net::post(strand_, ...)` 分发 2. **异步操作绑定** ```cpp http::async_read( *stream_, buffer_, res_, net::bind_executor( // 关键绑定 strand_, beast::bind_front_handler(...) ) ); ``` - `net::bind_executor` 确保回调在 strand 上下文中执行 - `beast::bind_front_handler` 适配成员函数 3. **线程安全状态管理** ```cpp net::post(strand_, [self = shared_from_this(), ...]() { // 所有状态访问都在strand内 self->request_.body() = content; }); ``` - 使用 `shared_from_this()` 确保对象生命周期 - 所有成员变量访问都在 strand 上下文中 4. **安全回调执行** ```cpp net::post(strand_, [self, errMsg, what]() { if (self->funcOnDone_) { self->funcOnDone_(...); } }); ``` - 用户回调在 strand 中执行,避免并发问题 - 防止回调执行期间对象被销毁 ### Strand 工作流程 ```mermaid sequenceDiagram participant Caller as 外部调用者 participant Strand as Strand队列 participant Socket as Socket操作 participant Callback as 用户回调 Caller->>Strand: post(Run) Strand->>Socket: async_connect Socket-->>Strand: OnConnect Strand->>Socket: async_write Socket-->>Strand: OnWrite Strand->>Socket: async_read Socket-->>Strand: OnRead Strand->>Callback: 安全执行funcOnDone_ ``` ### 性能优化技巧 1. **移动语义优化** ```cpp Run(..., const std::string& content, ...) { net::post(strand_, [self, content = std::move(content)]() {...}); } ``` 2. **Strand 局部化** ```cpp // 为不同资源使用独立strand net::strand read_strand_ = net::make_strand(ioc_); net::strand write_strand_ = net::make_strand(ioc_); ``` 3. **批处理操作** ```cpp void BatchRequests(const std::vector<Request>& requests) { net::post(strand_, [self, requests]() { for (auto& req : requests) { self->request_queue_.push(req); } if (self->request_queue_.size() == requests.size()) { self->ProcessBatch(); } }); } ``` 4. **动态超时调整** ```cpp void OnRead(..., int timeout) { // 根据响应大小调整后续超时 int new_timeout = CalculateTimeout(res_.body().size()); stream_->expires_after(std::chrono::milliseconds(new_timeout)); } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值