我之前在 《女朋友问我:什么时候用 C 而不用 C++?》这篇文章中说,C++ 与 C 语言的一个很大区别是,C++ 编译器在我们的代码背后偷偷加了许多代码,尤其是 C++11 有了明确的右值引用,引出移动构造和右值赋值(operator =(T&&)
)之后,这一点更加明显。
最近把 C++11 右值相关的内容重新复习了一下之后,加上使用右值技术改造了一些项目之后,甚是感慨,所以有了这篇文章。
先来看一段代码吧:
/**
* 版本1
*/
#include <iostream>
#include <functional>
#include <map>
#include <string>
#include <memory>
#include <cstdint>
enum class Method {
Get,
Post
};
struct Request {
};
struct Response {
int32_t data1;
std::string data2;
};
using Handler = std::function<bool(const Request& req, Response& resp)>;
struct HandlerItem {
Handler handler;
int32_t timeout;
};
class HttpSessionManger {
public:
bool registerRouter(const std::string& path, Method method, Handler handler, int32_t timeout = 5000) {
auto iter = m_routeHandlers.find(method);
if (iter == m_routeHandlers.end()) {
m_routeHandlers[method] = std::map<std::string, HandlerItem>();
}
HandlerItem handlerItem;
handlerItem.handler = handler;
handlerItem.timeout = timeout;
auto iter2 = m_routeHandlers[method].find(path);
if (iter2 != m_routeHandlers[method].end()) {
// 路由存在
return false;
}
m_routeHandlers[method][path] = handlerItem;
return true;
}
bool onRoute(const std::string& path, Method method, void* param) {
auto iter = m_routeHandlers.find(method);
if (iter == m_routeHandlers.end()) {
return false;
}
auto iter2 = iter->second.find(path);
if (iter2 == iter->second.end()) {
return false;
}
HandlerItem handlerItem = iter2->second;
Request req;
Response resp;
return handlerItem.handler(req, resp);
}
private:
//内层map的key为path,value为HandlerItem
std::map<Method, std::map<std::string, HandlerItem>> m_routeHandlers;
};
class HttpServer {
public:
void init(const std::string& ip, short port) {
m_spSessionManager = std::make_shared<HttpSessionManger>();
}
bool registerRouter(const std::string& path, Method method, Handler handler, int32_t timeout = 5000) {
return m_spSessionManager->registerRouter(path, method, handler, timeout);
}
bool run(const std::string& path, Method method, void* param) {
return m_spSessionManager->onRoute(path, method, param);
}
private:
std::shared_ptr<HttpSessionManger> m_spSessionManager;
};
int main()
{
HttpServer httpServer;
httpServer.init("0.0.0.0", 8888);
int32_t param1 = 123;
std::string param2 = "helloworld";
httpServer.registerRouter("/test", Method::Get, [¶m1, ¶m2](const Request& req, Response& resp) -> bool {
resp.data1 = param1;
resp.data2 = param2;
return true;
});
httpServer.run("/test", Method::Get, nullptr);
return 0;
}
这段代码模拟了一个 HttpServer 框架,对外暴露 HttpServer
类,HttpServer
类内部实际管理路由的类是 HttpSessionManager
,路由注册和调度都是对 HttpSessionManager
中的路由进行管理。
我们用 cppinsights
(https://cppinsights.io/)看下实际编译器为我们生成了什么样的代码。
#include <iostream>
#include <functional>
#include <map>
#include <string>
#include <memory>
#include <cstdint>
enum class Method : int
{
Get,
Post
};
struct Request
{
// inline constexpr Request() noexcept = default;
};
struct Response
{
int32_t data1;
std::basic_string<char> data2;
// inline ~Response() noexcept = default;
// inline Response() noexcept = default;
};
using Handler = std::function<bool(const Request&, Response&)>;
struct HandlerItem
{
std::function<bool(const Request&, Response&)> handler;
int32_t timeout;
// inline HandlerItem(const HandlerItem &) noexcept(false) = default;
// inline HandlerItem & operator=(const HandlerItem &) noexcept(false) = default;
// inline ~HandlerItem() noexcept = default;
// inline HandlerItem() noexcept = default;
};
class HttpSessionManger
{
public:
inline bool registerRouter(const std::basic_string<char>& path, Method method, std::function<bool(const Request&, Response&)> handler, int32_t timeout)
{
std::_Rb_tree_iterator<std::pair<const Method, std::map<std::basic_string<char>, HandlerItem, std::less<std::basic_string<char> >, std::allocator<std::pair<const std::basic_string<char>, HandlerItem> > > > > iter = std::_Rb_tree_iterator<std::pair<const Method, std::map<std::basic_string<char>, HandlerItem, std::less<std::basic_string<char> >, std::allocator<std::pair<const std::basic_string<char>, HandlerItem> > > > >(this->m_routeHandlers.find(method));
if (operator==(iter, this->m_routeHandlers.end())) {
this->m_routeHandlers.operator[](method).operator=(std::map<std::basic_string<char>, HandlerItem, std::less<std::basic_string<char> >, std::allocator<std::pair<const std::basic_string<char>, HandlerItem> > >());
}
HandlerItem handlerItem = HandlerItem();
handlerItem.handler.operator=(handler);
handlerItem.timeout = timeout;
std::_Rb_tree_iterator<std::pair<const std::basic_string<char>, HandlerItem> > iter2 = std::_Rb_tree_iterator<std::pair<const std::basic_string<char>, HandlerItem> >(this->m_routeHandlers.operator[](method).find(path));
if (operator!=(iter2, this->m_routeHandlers.operator[](method).end())) {
return false;
}
this->m_routeHandlers.operator[](method).operator[](path).operator=(handlerItem);
return true;
}
inline bool onRoute(const std::basic_string<char>& path, Method method, void* param)
{
std::_Rb_tree_iterator<std::pair<const Method, std::map<std::basic_string<char>, HandlerItem, std::less<std::basic_string<char> >, std::allocator<std::pair<const std::basic_string<char>, HandlerItem> > > > > iter = std::_Rb_tree_iterator<std::pair<const Method, std::map<std::basic_string<char>, HandlerItem, std::less<std::basic_string<char> >, std::allocator<std::pair<const std::basic_string<char>, HandlerItem> > > > >(this->m_routeHandlers.find(method));
if (operator==(iter, this->m_routeHandlers.end())) {
return false;
}
std::_Rb_tree_iterator<std::pair<const std::basic_string<char>, HandlerItem> > iter2 = std::_Rb_tree_iterator<std::pair<const std::basic_string<char>, HandlerItem> >(iter.operator->()->second.find(path));
if (operator==(iter2, iter.operator->()->second.end())) {
return false;
}
HandlerItem handlerItem = HandlerItem(iter2.operator->()->second);
Request req = Request();
Response resp = Response();
return handlerItem.handler.operator()(req, resp);
}
private:
std::map<Method, std::map<std::basic_string<char>, HandlerItem, std::less<std::basic_string<char> >, std::allocator<std::pair<const std::basic_string<char>, HandlerItem> > >, std::less<Method>, std::allocator<std::pair<const Method, std::map<std::basic_string<char>, HandlerItem, std::less<std::basic_string<char> >, std::allocator<std::pair<const std::basic_string<char>, HandlerItem> > > > > > m_routeHandlers;
public:
// inline ~HttpSessionManger() noexcept = default;
// inline HttpSessionManger() noexcept = default;
};
class HttpServer
{
public:
inline void init(const std::basic_string<char>& ip, short port)
{
this->m_spSessionManager.operator=(std::make_shared<HttpSessionManger>());
}
inline bool registerRouter(const std::basic_string<char>& path, Method method, std::function<bool(const Request&, Response&)> handler, int32_t timeout)
{
return static_cast<const std::__shared_ptr_access<HttpSessionManger, 2, 0, 0>&>(this->m_spSessionManager).operator->()->registerRouter(path, method, std::function<bool(const Request&, Response&)>(handler), timeout);
}
inline bool run(const std::basic_string<char>& path, Method method, void* param)
{
return static_cast<const std::__shared_ptr_access<HttpSessionManger, 2, 0, 0>&>(this->m_spSessionManager).operator->()->onRoute(path, method, param);
}
private:
std::shared_ptr<HttpSessionManger> m_spSessionManager;
public:
// inline constexpr HttpServer() noexcept = default;
// inline ~HttpServer() noexcept = default;
};
int main()
{
HttpServer httpServer = HttpServer();
httpServer.init(std::basic_string<char>("0.0.0.0", std::allocator<char>()), 8888);
int32_t param1 = 123;
std::basic_string<char> param2 = std::basic_string<char>(std::basic_string<char>("helloworld", std::allocator<char>()));
class __lambda_106_53
{
public:
inline bool operator()(const Request& req, Response& resp) const
{
resp.data1 = param1;
resp.data2.operator=(param2);
return true;
}
private:
int32_t& param1;
std::basic_string<char>& param2;
public:
// inline /*constexpr */ __lambda_106_53(const __lambda_106_53 &) noexcept = default;
// inline /*constexpr */ __lambda_106_53(__lambda_106_53 &&) noexcept = default;
__lambda_106_53(int& _param1, std::basic_string<char>& _param2)
: param1{ _param1 }
, param2{ _param2 }
{}
};
httpServer.registerRouter(std::basic_string<char>("/test", std::allocator<char>()), Method::Get, std::function<bool(const Request&, Response&)>(std::function<bool(const Request&, Response&)>(__lambda_106_53(__lambda_106_53{ param1, param2 }))), 5000);
httpServer.run(std::basic_string<char>("/test", std::allocator<char>()), Method::Get, nullptr);
return 0;
}
代码有点乱,主要是掺杂着许多模板的代码,我用红色字体标出哪些是编译器自动插入的代码。分析之后,我们可以得出以下结论:
class
和 struct
已经没本质区别了,struct
不再是 C 语言中那样的 plain object
,即使不显式定义结构体的构造函数、析构函数等,编译器也会为我们自动生成,因为一旦定义了一个结构体变量,就会调用其构造和析构函数。std::bind
一样,例如上述代码中注册路由的 lamda 表达最后就变成了类__lambda_106_53
。更精彩的事情还在后面,我们将版本 1 的代码修改一下,来看下版本 2:
/**
* 版本2
*/
#include <iostream>
#include <functional>
#include <map>
#include <string>
#include <memory>
#include <cstdint>
enum class Method {
Get,
Post
};
struct Request {
public:
Request() = default;
};
struct Response {
public:
Response() = default;
int32_t data1;
std::string data2;
};
using Handler = std::function<bool(const Request& req, Response& resp)>;
struct HandlerItem {
Handler handler;
int32_t timeout;
};
class HttpSessionManger {
public:
bool registerRouter(const std::string& path, Method method, Handler&& handler, int32_t timeout = 5000) {
auto iter = m_routeHandlers.find(method);
if (iter == m_routeHandlers.end()) {
m_routeHandlers.emplace(std::pair<Method, std::map<std::string, HandlerItem>>(method, std::move(std::map<std::string, HandlerItem>())));
}
HandlerItem handlerItem;
handlerItem.handler = std::move(handler);
handlerItem.timeout = timeout;
auto iter2 = m_routeHandlers[method].find(path);
if (iter2 != m_routeHandlers[method].end()) {
// 路由存在
return false;
}
m_routeHandlers[method].emplace(std::pair<std::string, HandlerItem>(path, std::move(handlerItem)));
return true;
}
bool onRoute(const std::string& path, Method method, void* param) {
auto iter = m_routeHandlers.find(method);
if (iter == m_routeHandlers.end()) {
return false;
}
auto iter2 = iter->second.find(path);
if (iter2 == iter->second.end()) {
return false;
}
HandlerItem& handlerItem = iter2->second;
Request req;
Response resp;
return handlerItem.handler(req, resp);
}
private:
//内层map的key为path,value为HandlerItem
std::map<Method, std::map<std::string, HandlerItem>> m_routeHandlers;
};
class HttpServer {
public:
void init(const std::string& ip, short port) {
m_spSessionManager = std::make_shared<HttpSessionManger>();
}
bool registerRouter(const std::string& path, Method method, Handler&& handler, int32_t timeout = 5000) {
return m_spSessionManager->registerRouter(path, method, std::move(handler), timeout);
}
bool run(const std::string& path, Method method, void* param) {
return m_spSessionManager->onRoute(path, method, param);
}
private:
std::shared_ptr<HttpSessionManger> m_spSessionManager;
};
int main()
{
HttpServer httpServer;
httpServer.init("0.0.0.0", 8888);
int32_t param1 = 123;
std::string param2 = "helloworld";
httpServer.registerRouter("/test", Method::Get, [¶m1, ¶m2](const Request& req, Response& resp) -> bool {
resp.data1 = param1;
resp.data2 = param2;
return true;
});
httpServer.run("/test", Method::Get, nullptr);
return 0;
}
我用红色字体标记出与版本 1 相比的改动之处。
我们再用 cppinsights 来看下编译器实际会生成什么代码:
#include <iostream>
#include <functional>
#include <map>
#include <string>
#include <memory>
#include <cstdint>
enum class Method : int
{
Get,
Post
};
struct Request
{
public:
inline constexpr Request() noexcept = default;
};
struct Response
{
public:
inline Response() noexcept = default;
int32_t data1;
std::basic_string<char> data2;
// inline ~Response() noexcept = default;
};
using Handler = std::function<bool(const Request&, Response&)>;
struct HandlerItem
{
std::function<bool(const Request&, Response&)> handler;
int32_t timeout;
// inline HandlerItem(HandlerItem &&) noexcept = default;
// inline ~HandlerItem() noexcept = default;
// inline HandlerItem() noexcept = default;
};
class HttpSessionManger
{
public:
inline bool registerRouter(const std::basic_string<char>& path, Method method, std::function<bool(const Request&, Response&)>&& handler, int32_t timeout)
{
std::_Rb_tree_iterator<std::pair<const Method, std::map<std::basic_string<char>, HandlerItem, std::less<std::basic_string<char> >, std::allocator<std::pair<const std::basic_string<char>, HandlerItem> > > > > iter = this->m_routeHandlers.find(method);
if (operator==(iter, this->m_routeHandlers.end())) {
this->m_routeHandlers.emplace<std::pair<Method, std::map<std::basic_string<char>, HandlerItem, std::less<std::basic_string<char> >, std::allocator<std::pair<const std::basic_string<char>, HandlerItem> > > > >(std::pair<Method, std::map<std::basic_string<char>, HandlerItem, std::less<std::basic_string<char> >, std::allocator<std::pair<const std::basic_string<char>, HandlerItem> > > >(method, std::move(std::map<std::basic_string<char>, HandlerItem, std::less<std::basic_string<char> >, std::allocator<std::pair<const std::basic_string<char>, HandlerItem> > >())));
}
HandlerItem handlerItem = HandlerItem();
handlerItem.handler.operator=(std::move(handler));
handlerItem.timeout = timeout;
std::_Rb_tree_iterator<std::pair<const std::basic_string<char>, HandlerItem> > iter2 = this->m_routeHandlers.operator[](method).find(path);
if (operator!=(iter2, this->m_routeHandlers.operator[](method).end())) {
return false;
}
this->m_routeHandlers.operator[](method).emplace<std::pair<std::basic_string<char>, HandlerItem> >(std::pair<std::basic_string<char>, HandlerItem>(path, std::move(handlerItem)));
return true;
}
inline bool onRoute(const std::basic_string<char>& path, Method method, void* param)
{
std::_Rb_tree_iterator<std::pair<const Method, std::map<std::basic_string<char>, HandlerItem, std::less<std::basic_string<char> >, std::allocator<std::pair<const std::basic_string<char>, HandlerItem> > > > > iter = this->m_routeHandlers.find(method);
if (operator==(iter, this->m_routeHandlers.end())) {
return false;
}
std::_Rb_tree_iterator<std::pair<const std::basic_string<char>, HandlerItem> > iter2 = iter.operator->()->second.find(path);
if (operator==(iter2, iter.operator->()->second.end())) {
return false;
}
HandlerItem& handlerItem = iter2.operator->()->second;
Request req = Request();
Response resp = Response();
return handlerItem.handler.operator()(req, resp);
}
private:
std::map<Method, std::map<std::basic_string<char>, HandlerItem, std::less<std::basic_string<char> >, std::allocator<std::pair<const std::basic_string<char>, HandlerItem> > >, std::less<Method>, std::allocator<std::pair<const Method, std::map<std::basic_string<char>, HandlerItem, std::less<std::basic_string<char> >, std::allocator<std::pair<const std::basic_string<char>, HandlerItem> > > > > > m_routeHandlers;
public:
// inline ~HttpSessionManger() noexcept = default;
// inline HttpSessionManger() noexcept = default;
};
class HttpServer
{
public:
inline void init(const std::basic_string<char>& ip, short port)
{
this->m_spSessionManager.operator=(std::make_shared<HttpSessionManger>());
}
inline bool registerRouter(const std::basic_string<char>& path, Method method, std::function<bool(const Request&, Response&)>&& handler, int32_t timeout)
{
return static_cast<const std::__shared_ptr_access<HttpSessionManger, 2, false, false>&>(this->m_spSessionManager).operator->()->registerRouter(path, method, std::move(handler), timeout);
}
inline bool run(const std::basic_string<char>& path, Method method, void* param)
{
return static_cast<const std::__shared_ptr_access<HttpSessionManger, 2, false, false>&>(this->m_spSessionManager).operator->()->onRoute(path, method, param);
}
private:
std::shared_ptr<HttpSessionManger> m_spSessionManager;
public:
// inline constexpr HttpServer() noexcept = default;
// inline ~HttpServer() noexcept = default;
};
int main()
{
HttpServer httpServer = HttpServer();
httpServer.init(std::basic_string<char>("0.0.0.0", std::allocator<char>()), 8888);
int32_t param1 = 123;
std::basic_string<char> param2 = std::basic_string<char>("helloworld", std::allocator<char>());
class __lambda_110_53
{
public:
inline /*constexpr */ bool operator()(const Request& req, Response& resp) const
{
resp.data1 = param1;
resp.data2.operator=(param2);
return true;
}
private:
int32_t& param1;
std::basic_string<char>& param2;
public:
// inline /*constexpr */ __lambda_110_53(const __lambda_110_53 &) noexcept = default;
// inline /*constexpr */ __lambda_110_53(__lambda_110_53 &&) noexcept = default;
__lambda_110_53(int& _param1, std::basic_string<char>& _param2)
: param1{ _param1 }
, param2{ _param2 }
{}
};
httpServer.registerRouter(std::basic_string<char>("/test", std::allocator<char>()), Method::Get, std::function<bool(const Request&, Response&)>(__lambda_110_53{ param1, param2 }), 5000);
httpServer.run(std::basic_string<char>("/test", std::allocator<char>()), Method::Get, nullptr);
return 0;
}
我也用红色字体标记出来,与上一个版本的不同,不过有一些不同的地方,在上述代码中看不出来,如对 std::map::emplace()
方法的调用,不知道读者注意到没有,在这个版本中,我能用引用的地方就用引用,能用右值的地方都使用 std::move
转换成了右值 ,可以 emplace
绝不 insert
。因此,编译器也为我们的 HandlerItem
结构体生成了移动构造函数(注意与拷贝构造函数区分),虽然这里的例子,对于这个结构体拷贝构造和移动构造性能上没啥差别。
本文到这里基本上就要结束了,有读者会问,你贴了这么多代码到底想说啥?我想说:
有读者在前面一篇文章《女朋友:一个 bug 查了两天,再解决不了,和你的代码过去吧!》中,有这样的代码:
void onAccept(int fd) {
auto pConnection = std::make_unique<HttpConnection>(fd);
//使用std::move将左值pConnection变成右值
auto pSession = std::make_shared<HttpSession>(std::move(pConnection), this);
auto clientID = pSession->getClientID();
{
std::lock_guard<std::mutex> scopedLock(m_sessionMutex);
m_mapSessions.emplace(clientID, pSession);
}
}
HttpSession
的构造函数是这样的:
class HttpSession {
public:
HttpSession(std::unique_ptr<HttpConnection>&& pConnection, HttpSessionManager* pSessionManager) : m_spConnection (std::move(pConnection)) {
m_clientID = pConnection->getIP() + ":" + pConnection->getPort() + ":" + generateUniqueID();
}
private:
std::unique_ptr<HttpConnection> m_spConnection;
};
有读者留言:传进来的参数 pConnection
已经在 onAccept
函数中使用 std::move
变成右值了,为啥在 HttpSession
的构造函数中又要使用 std::move
再转一次呢?
答案是,pConnection
传到 HttpSession
构造函数中时又变成了左值,我们创建 m_spConnection
对象时想使用移动构造函数(为了复用),所以需要再一次把 pConnection
使用 std::move
转换成右值。注意,右值引用不是右值,是左值。函数的实参是右值,函数实际执行时的形参不是右值,而是左值,尽管形参的函数参数的签名看起来像右值:
class HttpSession {
public:
//pConnection的类型看起来像右值
HttpSession(std::unique_ptr<HttpConnection>&& pConnection, ...) : m_spConnection (std::move(pConnection)) {
...
}
};
用一张图来梳理下这里的关系:
好啦,本文到此为止,感谢阅读,如果有不明白的地方,欢迎在文章下面留言。
本文是《女朋友要去 XXX 系列》第六篇,本系列:
篇三 《女朋友:一个 bug 查了两天,再解决不了,和你的代码过去吧!》
篇四《女朋友:七夕,你的 HttpServer 又崩了......》
篇五 《女朋友问:这些年你看过哪些网络编程书籍?》
推荐阅读
关注我,更多有趣实用的编程知识~
有帮助的话,点个赞呗