C++ 伪闭包

伪闭包

闭包允许函数捕获并携带其作用域中的变量，在闭包定义的作用域已经结束，这些变量仍然可以通过闭包访问，闭包也可以推迟操作，减少代码冗余。

与智能指针配合延长对象的生命周期。

Copy
class A {};

int foo() {
std::shared_ptr a_ptr = std::make_shared();
auto callback = [a_ptr]() {
// ...
};
async(callback); // 异步调用 callback
}
// 离开作用域后，a_ptr 智能指针引用计数减 1
// 如果回调还在执行，就还需要 a_ptr

案例代码：

Copy
// server.h
#include <boost/asio.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <boost/uuid/uuid_generators.hpp>
#include <memory>
#include <unordered_map>
#include <iostream>

namespace asio = boost::asio;

class Server;

class Session :
public std::enable_shared_from_this<Session> {

public:
Session(asio::io_context &ioc, Server *server);
~Session();
asio::ip::tcp::socket &getSocket();
const std::string &getUuid() const;
void start();

private:
void handleRead(
const boost::system::error_code &err,
std::size_t bytes_transferred,
std::shared_ptr<Session> session
);

void handleWrite(
const boost::system::error_code &err,
std::shared_ptr<Session> session
);

enum { MAX_MESSAGE_LEN = 1024 };
char data_[MAX_MESSAGE_LEN];
asio::ip::tcp::socket socket_;
std::string uuid_;
Server *server_;
};

class Server {
using SessionSPtr = std::shared_ptr<Session>;
public:
Server(asio::io_context &ioc, unsigned short port);
void removeSession(const std::string &uuid);

private:
void startAccept();

void handleAccept(
SessionSPtr session,
const boost::system::error_code &err
);
asio::io_context &ioc_;
unsigned short port_;
asio::ip::tcp::acceptor acceptor_;
std::unordered_map<std::string, SessionSPtr> sessions_;
};

Copy
// server.cpp
Session::Session(asio::io_context &ioc, Server *server) :
socket_(ioc),
server_(server) {
boost::uuids::uuid uuid = boost::uuids::random_generator()();
uuid_ = boost::uuids::to_string(uuid);
}

Session::~Session() {
std::cerr << "~Session()" << std::endl;
}

asio::ip::tcp::socket &Session::getSocket() {
return socket_;
}

const std::string &Session::getUuid() const {
return uuid_;
}

void Session::start() {
memset(data_, 0, MAX_MESSAGE_LEN);
socket_.async_read_some(
asio::buffer(data_, MAX_MESSAGE_LEN),
std::bind(
&Session::handleRead,
this,
std::placeholders::_1,
std::placeholders::_2,
shared_from_this() // 只是为了增加引用计数，防止在读回调时 this 对象已销毁
)
);
}

void Session::handleRead(
const boost::system::error_code &err,
std::size_t bytes_transferred,
std::shared_ptr<Session> self
) {
std::cout << "[handleRead] session use_count: " << self.use_count() << std::endl;

if (err.value() != 0) {
// 客户端关闭，服务器会收到一个读事件
std::cerr << "[error] [handleRead] client closed" << std::endl;
std::cerr << "[error] [handleRead] error msg: " << err.message() << std::endl;
std::cout << "[error] [handleRead] session use_count: " << self.use_count() << std::endl;
// 这里并没有真正移除 session
server_->removeSession(uuid_);
std::cerr << "[error] [handleWrite] session use_count: " << self.use_count() << std::endl;
return;
}

std::cout << "receive data: " << data_ << std::endl;

socket_.async_read_some(
asio::buffer(data_, MAX_MESSAGE_LEN),
std::bind(
&Session::handleRead,
this,
std::placeholders::_1,
std::placeholders::_2,
shared_from_this()
)
);

socket_.async_write_some(
asio::buffer(data_, bytes_transferred),
std::bind(
&Session::handleWrite,
this,
std::placeholders::_1,
shared_from_this()
)
);

std::cout << "[handleRead] session use_count: " << self.use_count() << std::endl;
}

void Session::handleWrite(
const boost::system::error_code &err,
std::shared_ptr<Session> self
) {
std::cout << "[handleWrite] session use_count: " << self.use_count() << std::endl;

if (err.value() != 0) {
std::cerr << "[error] [handleWrite] error msg: " << err.message() << std::endl;
std::cout << "[error] [handleWrite] session use_count: " << self.use_count() << std::endl;
// 这里并没有销毁 session
server_->removeSession(uuid_);
std::cerr << "[error] [handleWrite] session use_count: " << self.use_count() << std::endl;
return;
}

socket_.async_read_some(
asio::buffer(data_, MAX_MESSAGE_LEN),
std::bind(
&Session::handleRead,
this,
std::placeholders::_1,
std::placeholders::_2,
shared_from_this()
)
);

std::cout << "[handleWrite] session use_count: " << self.use_count() << std::endl;
}

Server::Server(
asio::io_context &ioc,
unsigned short port
) :
ioc_(ioc),
port_(port),
acceptor_(ioc_, asio::ip::tcp::endpoint(asio::ip::tcp::v4(), port_)) {
std::cout << "server start listen " << port_ << std::endl;
startAccept();
}

void Server::removeSession(const std::string &uuid) {
sessions_.erase(uuid);
}

void Server::startAccept() {
SessionSPtr session = std::make_shared<Session>(ioc_, this);
acceptor_.async_accept(
session->getSocket(),
std::bind(
&Server::handleAccept,
this,
session,
std::placeholders::_1
)
);
}

void Server::handleAccept(
SessionSPtr session,
const boost::system::error_code &err
) {
if (err.value() != 0) {
std::cerr << err.message() << std::endl;
return;
}
std::cout << "client ip: "
<< session->getSocket().remote_endpoint().address().to_string()
<< std::endl;

sessions_[session->getUuid()] = session;
session->start();

startAccept();
}

int main(int argc, char *argv[]) {
try {
asio::io_context ioc;
Server server(ioc, 11451);
ioc.run();
} catch (std::exception &exp) {
std::cerr << "[exception]: " << exp.what() << std::endl;
}
return 0;
}

延迟操作减少代码冗余。

Copy
class Defer {
public:
Defer(const std::function<void()> &&func) :
func_(func) {

}
~Defer() {
func_();
}

private:
std::function<void()> func_;
};

int main() {
int *a = new int(1);
if (!a) {
return;
}
Defer defer([&]() {
delete a;
});
// ...
}
// defer 离开作用域后，调用函数来回收资源

案例代码：

Copy
bool MysqlManager::removeUser(
const std::string &name
) {
std::unique_ptr<SqlConnection> connection \
= mysql_connection_pool_->getConnection();
if (!connection) {
ERROR("connection is null");
return false;
}

// 推迟 connection 的回收
Defer defer([&]() {
mysql_connection_pool_->recycleConnection(
std::move(connection)
);
});

try {
std::unique_ptr<sql::PreparedStatement> stmt(
connection->m_connection->prepareStatement(
"DELETE FROM user WHERE name = ?"
)
);
stmt->setString(1, name);
int res = stmt->executeUpdate();
INFO("remove user %d", res);
return true;
} catch (sql::SQLException &exp) {
ERROR("SQL Exception: %s", exp.what());
ERROR("SQL error code: %d", exp.getErrorCode());
ERROR("SQL State: %d", exp.getSQLState().c_str());
return -1;
}
}