C++11 std::make_shared 与 std::shared_ptr双剑合璧

项目做完上线,发现存在内存泄漏。因为客户端链接到服务器时传统new出来对象,断开链接后没有进行释放。

一、定时检测对象释放情况

代码如下:

//1、开启服务监听
bool CWebServer::StartServer(const std::string& strIp, uint16 nPort)
{
	try{
		boost::system::error_code ec;
#if BOOST_VERSION >= 106600
		boost::asio::ip::address addr = boost::asio::ip::make_address(strIp, ec);
#else
		boost::asio::ip::address addr = boost::asio::ip::address::from_string(strIp, ec);
#endif
		if(ec)
		{
			std::cout << "error message:" << ec.message() << std::endl;
		}

		boost::asio::ip::tcp::endpoint endpoint(addr,nPort);

		m_strIP = strIp;
		m_usPort = nPort;
		m_acceptor = new boost::asio::ip::tcp::acceptor(sIOService->GetIOService(),endpoint,true);
		//2019/04/29 add
		m_acceptor->set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));

	}catch(boost::system::error_code ec)
	{
		TC_LOG_ERROR("apilib.log", "%s message:%s", __FUNCTION__, ec.message().c_str());
	}
	return true;
}

//2、new 出来的CWebClient对象没有释放
CWebClient* client = new CWebClient(get_io_context(m_acceptor));
	m_acceptor->async_accept(client->get_socket(), boost::bind(&CWebServer::accept_handler, this, client, boost::asio::placeholders::error));

//3、add_client(client); 加入链接对象
void CWebServer::accept_handler(CWebClient* client, const boost::system::error_code &ec)
{
	if (ec)
	{
		client->conn_error_handler(ec);
		start();
		return;
	}
	try
	{
		start();
		boost::asio::ip::tcp::endpoint remote = client->get_socket().remote_endpoint();
		client->SetIP(remote.address().to_string());
		client->init(m_pOnEnter, m_pOnMsg, m_pOnExit);
		client->start();
		add_client(client); 
	}
	catch (boost::system::error_code ec)
	{
		TC_LOG_ERROR("apilib.log", "%s message:%s", __FUNCTION__, ec.message().c_str());
	}
}

//4、保存客户端连接socket
void CWebServer::add_client(CWebClient* client)
{
	m_mapClient.insert(std::make_pair(client->GetHandleID(), client));
}

然后发现当客户端链接断开的时候,没有地方释放new出来的对象(水平有限没有使用对象池,频繁new delete对象不好,后期学习进而优化)。

修改代码如下:

//1、添加async_wait定时回调函数update
bool CWebServer::StartServer(const std::string& strIp, uint16 nPort)
{
	try{
		boost::system::error_code ec;
#if BOOST_VERSION >= 106600
		boost::asio::ip::address addr = boost::asio::ip::make_address(strIp, ec);
#else
		boost::asio::ip::address addr = boost::asio::ip::address::from_string(strIp, ec);
#endif
		if(ec)
		{
			std::cout << "error message:" << ec.message() << std::endl;
		}

		boost::asio::ip::tcp::endpoint endpoint(addr,nPort);

		m_strIP = strIp;
		m_usPort = nPort;
		m_acceptor = new boost::asio::ip::tcp::acceptor(sIOService->GetIOService(),endpoint,true);
		//2019/04/29 add
		m_acceptor->set_option(boost::asio::ip::tcp::acceptor::reuse_address(true));

		//2019/05/09 add
		m_updateTimer.expires_from_now(boost::posix_time::milliseconds(1000));
		m_updateTimer.async_wait(std::bind(&CWebServer::update, this));

	}catch(boost::system::error_code ec)
	{
		TC_LOG_ERROR("apilib.log", "%s message:%s", __FUNCTION__, ec.message().c_str());
	}
	return true;
}

//2、不断检测连接是否断开 delete释放对象
void CWebServer::update()
{
	m_updateTimer.expires_from_now(boost::posix_time::milliseconds(1000));
	m_updateTimer.async_wait(std::bind(&CWebServer::update, this));

	for (std::map<uint32, CWebClient*>::iterator it = m_mapClient.begin(); it != m_mapClient.end(); )
	{
		if (it->second->CloseStatus())
		{
			delete(it->second);
			m_mapClient.erase(it++);
		}
		else
		{
			++it;
		}
	}
}

说明:这样就能够保持正常new delete对象。

二、改写服务代码,使用智能指针管理内存

//1、客户端链接对象继承enable_shared_from_this模板类
class CWebClient : public std::enable_shared_from_this<CWebClient>{...};

//2、使用make_shared分配内存对象,一次分配防止出错
void CWebServer::start()
{
#if TC_SOCKET_TYPE == TC_SOCKET_SHARED_PTR
	std::shared_ptr<CWebClient> client = std::make_shared<CWebClient>(get_io_context(m_acceptor));
	m_acceptor->async_accept(client->get_socket(), boost::bind(&CWebServer::accept_handler, this, client, boost::asio::placeholders::error));
#else
	CWebClient* client = new CWebClient(get_io_context(m_acceptor));
	m_acceptor->async_accept(client->get_socket(), boost::bind(&CWebServer::accept_handler, this, client, boost::asio::placeholders::error));
#endif
}

//3、异步接受改写
#if TC_SOCKET_TYPE == TC_SOCKET_SHARED_PTR
void CWebServer::accept_handler(std::shared_ptr<CWebClient> client, const boost::system::error_code &ec)
#else 
void CWebServer::accept_handler(CWebClient* client, const boost::system::error_code &ec)
#endif
{
	if (ec)
	{
		client->conn_error_handler(ec);
		start();
		return;
	}
	try
	{
		start();
		boost::asio::ip::tcp::endpoint remote = client->get_socket().remote_endpoint();
		client->SetIP(remote.address().to_string());
		client->init(m_pOnEnter, m_pOnMsg, m_pOnExit);
		client->start();
		add_client(client); 
	}
	catch (boost::system::error_code ec)
	{
		TC_LOG_ERROR("apilib.log", "%s message:%s", __FUNCTION__, ec.message().c_str());
	}
}

//4、server链接存储改写
#if TC_SOCKET_TYPE == TC_SOCKET_SHARED_PTR
void CWebServer::add_client(std::shared_ptr<CWebClient> client)
#else 
void CWebServer::add_client(CWebClient* client)
#endif
{
	m_mapClient.insert(std::make_pair(client->GetHandleID(), client));
}

//5、检测释放改写
void CWebServer::update()
{
	m_updateTimer.expires_from_now(boost::posix_time::milliseconds(1000));
	m_updateTimer.async_wait(std::bind(&CWebServer::update, this));

#if TC_SOCKET_TYPE == TC_SOCKET_SHARED_PTR
	for (std::map<uint32, std::shared_ptr<CWebClient>>::iterator it = m_mapClient.begin(); it != m_mapClient.end(); )
#else
	for (std::map<uint32, CWebClient*>::iterator it = m_mapClient.begin(); it != m_mapClient.end(); )
#endif
	{
		if (it->second->CloseStatus())
		{
#if TC_SOCKET_TYPE == TC_SOCKET_NORMAL_PTR
			delete(it->second);
#endif
			m_mapClient.erase(it++);
		}
		else
		{
			++it;
		}
	}
}

说明:

1、继承enable_shared_from_this<CWebClient>模板类,必须要是weak_ptr弱引用计数>0,所以使用std::make_shared内部会调用shared_ptr构造,进而构造weak_ptr。

2、delete(it->second)就不需要调用了,在m_mapClient.erase的时候,对象内存就会自动释放,这就是智能指针的好处。

3、如何判断对象是否释放。

1)先去获取对象,日志输出为0,等它释放后,调用expired()函数,日志输出为1,就表示成功释放。

2)我们换个方式,调试看看效果。

看见没,释放前,1个强引用shared_ptr,2个弱引用weak_ptr。 我们继续下一步。

释放后,只有1个弱引用,就是当前pWeak引用。其实这里就应经知道对象释放了,再继续下一步。

 pWeak调用lock()获取shared_ptr对象为empty。进一步表明对象不存在,已经自动被释放。

注:1、测试说明std::cout,项目中还是严谨些好。2、智能指针,代码有误,请指正,谢谢。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值