擬似クロージャ#
クロージャは関数がそのスコープ内の変数をキャプチャし、持ち運ぶことを許可します。クロージャが定義されたスコープが終了した後でも、これらの変数にはクロージャを通じてアクセスできます。また、クロージャは操作を遅延させることができ、コードの冗長性を減らします。
スマートポインタと組み合わせてオブジェクトのライフサイクルを延長します。
class A {};
int foo() {
std::shared_ptr<A> a_ptr = std::make_shared<A>();
auto callback = [a_ptr]() {
// ...
};
async(callback); // 非同期で callback を呼び出す
}
// スコープを離れると、a_ptr スマートポインタの参照カウントが 1 減る
// コールバックがまだ実行中であれば、a_ptr が必要です
サンプルコード:
// 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_;
};
// 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;
// ここでは実際にセッションを削除していません
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;
// ここではセッションを破棄していません
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;
}
遅延操作によりコードの冗長性を減らします。
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 がスコープを離れると、関数が呼び出されてリソースが回収されます
サンプルコード:
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;
}
}