介绍
boost的enable_shared_from_this是一个模板类,它的使用是一个基于成员函数返回shared_ptr。有关shared_ptr的使用,请参考我的另一篇文章boost shared_ptr的使用。
boost::enable_shared_from_this主要就是shared_from_this这个函数,它的主要代码如下:
namespace boost
{
template<class T> class enable_shared_from_this
{
public:
shared_ptr<T> shared_from_this();
shared_ptr<T const> shared_from_this() const;
}
}
返回的都是shared_ptr,只不过一个是const一个是非const,很好理解。
此处参考boost官网的一个例子:
#include <boost/enable_shared_from_this.hpp>
#include <boost/shared_ptr.hpp>
#include <cassert>
class Y: public boost::enable_shared_from_this<Y>
{
public:
boost::shared_ptr<Y> f()
{
return shared_from_this();
}
};
int main()
{
boost::shared_ptr<Y> p(new Y);
boost::shared_ptr<Y> q = p->f();
assert(p == q);
assert(!(p < q || q < p)); // p and q must share ownership
}
类Y继承自boost::enable_shared_from_this<Y>,很多初学者看到这里就感到很奇怪,怎么自己继承的地方模板参数还是自己,其实,稍微对泛型编程有点了解就知道,这里把Y当成模板参数,然后我们看看enable_shared_from_this这个模板类的实现,就会发现,Y参数只是用来shared_ptr的模板参数,意思就是说enable_shared_from_this的函数shared_from_this只是基于this指针构造了一个shared_ptr智能指针返回。想必现在大家都理解清楚了,一个类继承自boost::enable_shared_from_this,可以再自己类的内部用shared_from_this来引用自身。那么问题来了,如果不用new来定义对象会怎样呢?
大家看看下面2段代码的区别:
class Y: public boost::enable_shared_from_this<Y>
{
public:
boost::shared_ptr<Y> f()
{
return shared_from_this();
}
void test()
{
std::cout << "test" << std::endl;
}
};
int main()
{
Y y;
y.test();
}
与
class Y: public boost::enable_shared_from_this<Y>
{
public:
boost::shared_ptr<Y> f()
{
return shared_from_this();
}
void test()
{
std::cout << "test" << std::endl;
}
};
int main()
{
Y y;
boost::shared_ptr<Y> pY = y.f();
assert(nullptr != pY);
pY->test();
}
运行下,就可以知道结果,为什么会有这种结果呢,很简单,因为上面说得很清楚,enable_shared_from_this是把this指针包装成智能指针,那如果你是不是new出来的对象,而是在栈上分配的对象,你不引用还好,如果你一旦用了shared_from_this函数,那么,你自己想想,你这块内存到底是由系统给你释放,还是由智能指针给你释放呢?这必然造成冲突。
实践
boost::enable_shared_from_this主要用于什么地方呢?我第一次见到它也是再boost.asio库中见到,也是一个很好的Proactor跨平台网络库。以下是boost官网的一个小例子:
//
// async_tcp_echo_server.cpp
// ~~~~~~~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2014 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include <cstdlib>
#include <iostream>
#include <boost/bind.hpp>
#include <boost/asio.hpp>
using boost::asio::ip::tcp;
class session
{
public:
session(boost::asio::io_service& io_service)
: socket_(io_service)
{
}
tcp::socket& socket()
{
return socket_;
}
void start()
{
socket_.async_read_some(boost::asio::buffer(data_, max_length),
boost::bind(&session::handle_read, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
private:
void handle_read(const boost::system::error_code& error,
size_t bytes_transferred)
{
if (!error)
{
boost::asio::async_write(socket_,
boost::asio::buffer(data_, bytes_transferred),
boost::bind(&session::handle_write, this,
boost::asio::placeholders::error));
}
else
{
delete this;
}
}
void handle_write(const boost::system::error_code& error)
{
if (!error)
{
socket_.async_read_some(boost::asio::buffer(data_, max_length),
boost::bind(&session::handle_read, this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
else
{
delete this;
}
}
tcp::socket socket_;
enum { max_length = 1024 };
char data_[max_length];
};
class server
{
public:
server(boost::asio::io_service& io_service, short port)
: io_service_(io_service),
acceptor_(io_service, tcp::endpoint(tcp::v4(), port))
{
start_accept();
}
private:
void start_accept()
{
session* new_session = new session(io_service_);
acceptor_.async_accept(new_session->socket(),
boost::bind(&server::handle_accept, this, new_session,
boost::asio::placeholders::error));
}
void handle_accept(session* new_session,
const boost::system::error_code& error)
{
if (!error)
{
new_session->start();
}
else
{
delete new_session;
}
start_accept();
}
boost::asio::io_service& io_service_;
tcp::acceptor acceptor_;
};
int main(int argc, char* argv[])
{
try
{
if (argc != 2)
{
std::cerr << "Usage: async_tcp_echo_server <port>\n";
return 1;
}
boost::asio::io_service io_service;
using namespace std; // For atoi.
server s(io_service, atoi(argv[1]));
io_service.run();
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
对,没错,首先,我让大家看的是没有用enable_shared_from_this的例子,看到没有,看到地方出来了delete操作,这还只是一个很简单的echo服务,真正的产品是不可能有这么简单的,其中判断条件很多,那么,难道要在每个错误的地方都用delete释放内存吗?如果忘记了1,2个地方呢?那势必造成内存泄漏。所以,这里是一个利用enable_shared_from_this的好地方:
#include <cstdlib>
#include <iostream>
#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <boost/enable_shared_from_this.hpp>
using boost::asio::ip::tcp;
class session : public boost::enable_shared_from_this<session>
{
public:
session(boost::asio::io_service& io_service)
: socket_(io_service)
{
}
tcp::socket& socket()
{
return socket_;
}
void start()
{
socket_.async_read_some(boost::asio::buffer(data_, max_length),
boost::bind(&session::handle_read, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
private:
void handle_read(const boost::system::error_code& error,
size_t bytes_transferred)
{
if (!error)
{
boost::asio::async_write(socket_,
boost::asio::buffer(data_, bytes_transferred),
boost::bind(&session::handle_write, shared_from_this(),
boost::asio::placeholders::error));
}
}
void handle_write(const boost::system::error_code& error)
{
if (!error)
{
socket_.async_read_some(boost::asio::buffer(data_, max_length),
boost::bind(&session::handle_read, shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
}
tcp::socket socket_;
enum { max_length = 1024 };
char data_[max_length];
};
typedef boost::shared_ptr<session> sessionPtr;
class server
{
public:
server(boost::asio::io_service& io_service, short port)
: io_service_(io_service),
acceptor_(io_service, tcp::endpoint(tcp::v4(), port))
{
start_accept();
}
private:
void start_accept()
{
sessionPtr new_session(new session(io_service_));
acceptor_.async_accept(new_session->socket(),
boost::bind(&server::handle_accept, this, new_session,
boost::asio::placeholders::error));
}
void handle_accept(const sessionPtr& new_session,
const boost::system::error_code& error)
{
if (!error)
{
new_session->start();
}
start_accept();
}
boost::asio::io_service& io_service_;
tcp::acceptor acceptor_;
};
int main(int argc, char* argv[])
{
try
{
if (argc != 2)
{
std::cerr << "Usage: async_tcp_echo_server <port>\n";
return 1;
}
boost::asio::io_service io_service;
using namespace std; // For atoi.
server s(io_service, atoi(argv[1]));
io_service.run();
}
catch (std::exception& e)
{
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
现在看看,一个小小的改变,是不是让代码看起来优雅多了呢?而且根本不用去担心内存泄漏的问题。所有的内存问题都交由shared_ptr去管理了。这里主要阐述下以上的原理:
1.首先继承自enable_shared_from_this
2.然后用shared_ptr去存储new出来的对象
3.接着调用start函数去接收数据,在这里,bind函数的参数是shared_from_this,所以,bind会持有这个智能指针,session不会被释放掉,直到bind回调handle_read
4.在handle_read回调中,如果没有错误,则把shared_from_this传递给bind继续持有,否则,释放内存
5.在handle_write回调中对智能指针的处理结果同4.
在整个过程中,你完全不必去担心你new出来的对象什么时候释放或者是由谁来释放,完全由boost给你一手承包,让你专注在业务处理中。好了,今天的enable_shared_from_this就介绍到这里了。