banner
cells

cells

为美好的世界献上 bug

C++ Pseudo-Closure

Pseudo Closure#

Closures allow functions to capture and carry variables from their scope, and these variables can still be accessed through the closure even after the scope in which the closure is defined has ended. Closures can also delay operations, reducing code redundancy.

Used in conjunction with smart pointers to extend the lifecycle of objects.

class A {};

int foo() {
    std::shared_ptr<A> a_ptr = std::make_shared<A>();
    auto callback = [a_ptr]() {
        // ...
    };
    async(callback); // Asynchronous call to callback
}
// After leaving the scope, the reference count of the a_ptr smart pointer decreases by 1
// If the callback is still executing, a_ptr is still needed

Example code:

// 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() // Just to increase the reference count to prevent this object from being destroyed during the read callback
        )
    );
}

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) {
        // Client closed, the server will receive a read event
        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;
        // The session is not actually removed here
        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;
        // The session is not destroyed here
        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;
}

Delayed operations reduce code redundancy.

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;
    });
    // ...
}
// The defer calls the function to reclaim resources after leaving the scope

Example code:

bool MysqlManager::removeUser(
    const std::string &name
) {
    std::unique_ptr<SqlConnection> connection \
        = mysql_connection_pool_->getConnection();
    if (!connection) {
        ERROR("connection is null");
        return false;
    }
    
    // Delay the reclamation of 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;
    }
}
Loading...
Ownership of this post data is guaranteed by blockchain and smart contracts to the creator alone.