大型模板源码阅读
概述
- 阅读大型模板项目时,最容易犯的错误是
- 从公共
API跳进第一个模板定义,然后试图把所有分支、所有类型和所有平台代码全部看懂
- 从公共
- 正确方法是先固定一个具体调用,只追踪它实际经过的路径
|
1 2 3 4 5 6 7 |
socket.async_receive( buffer, [](const std::error_code& error, std::size_t bytes) { // ... } ); |
- 我们只关心:
- 这个调用中每个模板参数是什么
- 最终选中了哪个重载
- 哪些对象被复制或移动
- 谁保存
buffer和handler - 真正的系统调用在哪里
- 完成后在哪个线程调用
handler
阅读模板源码的四条线
- 不要只追踪函数调用。大型模板库至少要同时追踪四条线
| 追踪线 | 需要回答的问题 |
| 类型流 | T、Handler、Args... 最终是什么类型 |
| 选择流 | 哪个重载、Concept、tag 或特化被选中 |
| 对象流 | 对象是引用、复制、移动还是类型擦除?谁拥有它? |
| 执行流 | 哪一层开始执行真实运行时代码?完成在哪个线程发生 |
- 例如看到
|
1 |
std::forward<Handler>(handler) |
|
1 2 3 4 5 |
// 只知道发生了完美转发还不够。还要知道 Handler 是 Lambda,还是 Lambda&? 目标对象按值保存,还是继续保存引用? 转发之后谁负责销毁? |
一个可运行的“仿 Asio”示例
- 保留了大型异步库的典型分层
|
1 2 3 4 5 |
公共 API → token/handler 适配 → initiation → operation → 运行时核心 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 |
#include <concepts> #include <cstddef> #include <functional> #include <iostream> #include <span> #include <system_error> #include <type_traits> #include <utility> #include <vector> template<class Handler> concept ReceiveHandler = requires( Handler& handler, const std::error_code& error, std::size_t bytes) { { std::invoke(handler, error, bytes) } -> std::same_as<void>; }; template<class Token> concept ReceiveToken = ReceiveHandler<std::decay_t<Token>> && std::constructible_from< std::decay_t<Token>, Token >; template<ReceiveHandler Handler> class ReceiveOperation { private: std::span<std::byte> buffer_; Handler handler_; public: ReceiveOperation( std::span<std::byte> buffer, Handler handler) : buffer_(buffer), handler_(std::move(handler)) { } void start() { // 教学示例在这里同步“完成”。 // 真实异步库会提交给 IOCP/epoll 等后端。 std::cout << "ReceiveOperation::start\n"; std::error_code error; std::invoke( handler_, error, buffer_.size() ); } }; template<class Initiation, class CompletionToken> requires ReceiveToken<CompletionToken> void async_initiate( Initiation&& initiation, CompletionToken&& token) { using Handler = std::decay_t<CompletionToken>; Handler handler( std::forward<CompletionToken>(token) ); std::forward<Initiation>(initiation)( std::move(handler) ); } class Socket { private: struct InitiateReceive { std::span<std::byte> buffer; template<ReceiveHandler Handler> void operator()(Handler handler) const { ReceiveOperation<Handler> operation{ buffer, std::move(handler) }; operation.start(); } }; public: template<class CompletionToken> requires ReceiveToken<CompletionToken> void async_receive( std::span<std::byte> buffer, CompletionToken&& token) { async_initiate( InitiateReceive{buffer}, std::forward<CompletionToken>(token) ); } }; int main() { Socket socket; std::vector<std::byte> storage(128); socket.async_receive( std::span<std::byte>{storage}, [](const std::error_code& error, std::size_t bytes) { if (!error) { std::cout << "received " << bytes << " bytes\n"; } } ); } |
|
1 2 |
ReceiveOperation::start received 128 bytes |
第一步:固定具体调用
- 从调用点开始
|
1 2 3 4 5 6 |
socket.async_receive( std::span<std::byte>{storage}, [](const std::error_code&, std::size_t) { } ); |
- 先给
lambda起一个假想名字
|
1 2 3 4 5 6 7 8 |
struct LambdaType { void operator()( const std::error_code&, std::size_t) const; }; // 实际 lambda 类型是编译器生成的唯一匿名类型,但阅读时可以把它记作 LambdaType |
- 于是当前调用的类型账本是
| 名字 | 具体类型 |
buffer |
std::span<std::byte> |
CompletionToken |
LambdaType |
CompletionToken&& |
LambdaType&& |
decay_t<CompletionToken> |
LambdaType |
Handler |
LambdaType |
Initiation |
Socket::InitiateReceive |
- 如果传入命名
lambda
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
auto handler = [](const std::error_code&, std::size_t) { }; socket.async_receive(buffer, handler); // handler 是左值,因此 CompletionToken = LambdaType& CompletionToken&& = LambdaType& // 引用折叠 decay_t<CompletionToken> = LambdaType // 因此框架准备复制这个左值 handler |
- 如果
lambda捕获了不可复制对象
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
auto handler = [ptr = std::make_unique<int>(42)] (const std::error_code&, std::size_t) { }; // 它是仅移动类型。作为左值传入 socket.async_receive(buffer, handler); // 不能复制 // 约束 constructible_from< decay_t<Token>, Token > // 会失败 // 需要移动 socket.async_receive( buffer, std::move(handler) ); |
第二步:为每一层分类
| 层 | 角色 | 是否做真实I/O |
Socket::async_receive |
公共 API 门面 |
否 |
ReceiveToken |
编译期约束 | 否 |
async_initiate |
token/handler 适配层 |
否 |
InitiateReceive::operator() |
创建异步操作 | 否 |
ReceiveOperation |
保存异步状态 | 准备执行 |
ReceiveOperation::start() |
运行时入口 | 是/提交后端 |
第三步:逐层代入具体类型
|
1 2 3 4 5 6 7 8 9 10 11 |
// 公共接口 template<class CompletionToken> requires ReceiveToken<CompletionToken> void async_receive( std::span<std::byte> buffer, CompletionToken&& token); // 代入后近似为: void async_receive( std::span<std::byte> buffer, LambdaType&& token); |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
// 下一层 async_initiate( InitiateReceive{buffer}, std::forward<LambdaType>(token) ); using Handler = std::decay_t<CompletionToken>; using Handler = LambdaType; // 把 handler 变成框架拥有的对象 InitiateReceive{buffer}( std::move(handler) ); // 最终创建 ReceiveOperation<LambdaType> |
第四步:找到第一个持久状态对象
- 异步代码最重要的通常不是公共
API,而是operation
|
1 2 3 4 5 6 |
template<class Handler> class ReceiveOperation { std::span<std::byte> buffer_; Handler handler_; }; |
- 现在检查所有权
| 成员 | 是否拥有底层资源 |
handler_ |
是,按值保存 |
buffer_ |
否,只保存地址和长度 |
Socket |
这个简化 operation 没有保存 |
storage |
由调用方拥有 |
|
1 2 3 |
// 因此真实异步版本必须保证 storage // 在 handler 被调用之前一直存活 |
|
1 2 3 4 5 6 7 8 9 10 11 12 |
// 下面可能悬空 void start(Socket& socket) { std::vector<std::byte> buffer(1024); socket.async_receive( buffer, [](const auto&, std::size_t) { } ); } // 如果操作尚未完成,buffer 已销毁 |
- 为什么可以使用局部
operation
|
1 2 3 4 5 6 7 8 9 |
// 示例 // 在 start() 返回前就同步调用了 handler,所以局部 operation 没有问题 ReceiveOperation<Handler> operation; operation.start(); // 真实异步库不能这样做 operation.start(); return; // operation 被销毁,但系统操作尚未完成 |
第五步:只追踪被选中的重载
- 不要同时深入三个函数
|
1 2 3 4 5 6 7 8 9 10 11 |
template<class T> requires MemberEncodable<T> void encode(T&&); template<class T> requires AdlEncodable<T> void encode(T&&); template<class T> requires TriviallyEncodable<T> void encode(T&&); |
类型别名的追踪方法
- 不要试图一直在脑中展开
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
using token_type = typename async_result< std::decay_t<Token>, Signature >::completion_handler_type; using operation_type = receive_operation< Socket, Buffer, token_type, Executor, Allocator >; |
|
1 2 3 4 5 6 7 8 9 10 |
// 建立类型账本 // 遇到新的别名就追加一行 Token = MyHandler& decay_t<Token> = MyHandler Signature = void(error_code, size_t) token_type = MyHandler Executor = io_context::executor_type Allocator = std::allocator<void> operation_type = receive_operation<...> |
真实 Asio 风格的主线
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
sync_read_some(buffer, token) ↓ 检查 completion signature ↓ async_initiate / async_result ↓ token 转换成具体 handler 和返回类型 ↓ initiation 创建 operation ↓ 关联 executor / allocator / cancellation slot ↓ operation 提交到 socket service ↓ IOCP、epoll、select 或其他后端 ↓ 完成队列返回 ↓ operation 的 complete/do_complete ↓ executor 调度 ↓ 调用用户 handler |
综合
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 |
#include <concepts> #include <cstddef> #include <cstdint> #include <functional> #include <iostream> #include <limits> #include <span> #include <stdexcept> #include <string> #include <string_view> #include <tuple> #include <type_traits> #include <utility> #include <variant> #include <vector> namespace wire { inline constexpr std::size_t max_payload_size = 64 * 1024; class Writer { public: void write_u16(std::uint16_t value) { bytes_.push_back(std::byte{static_cast<unsigned char>(value >> 8)}); bytes_.push_back(std::byte{static_cast<unsigned char>(value)}); } void write_u32(std::uint32_t value) { for (int shift = 24; shift >= 0; shift -= 8) { bytes_.push_back(std::byte{ static_cast<unsigned char>(value >> shift)}); } } void write_u64(std::uint64_t value) { for (int shift = 56; shift >= 0; shift -= 8) { bytes_.push_back(std::byte{ static_cast<unsigned char>(value >> shift)}); } } void write_string(std::string_view value) { if (value.size() > std::numeric_limits<std::uint16_t>::max()) { throw std::length_error{"string is too large"}; } write_u16(static_cast<std::uint16_t>(value.size())); for (char ch : value) { bytes_.push_back(std::byte{static_cast<unsigned char>(ch)}); } } void append(std::span<const std::byte> bytes) { bytes_.insert(bytes_.end(), bytes.begin(), bytes.end()); } [[nodiscard]] std::span<const std::byte> bytes() const noexcept { return bytes_; } [[nodiscard]] std::vector<std::byte> release() && { return std::move(bytes_); } private: std::vector<std::byte> bytes_; }; class Reader { public: explicit Reader(std::span<const std::byte> bytes) : bytes_(bytes) {} bool read_u16(std::uint16_t& value) { if (remaining() < 2) { return false; } value = (to_u8(bytes_[position_]) << 8) | to_u8(bytes_[position_ + 1]); position_ += 2; return true; } bool read_u32(std::uint32_t& value) { if (remaining() < 4) { return false; } value = 0; for (std::size_t i = 0; i < 4; ++i) { value = (value << 8) | to_u8(bytes_[position_ + i]); } position_ += 4; return true; } bool read_u64(std::uint64_t& value) { if (remaining() < 8) { return false; } value = 0; for (std::size_t i = 0; i < 8; ++i) { value = (value << 8) | to_u8(bytes_[position_ + i]); } position_ += 8; return true; } bool read_string(std::string& value) { std::uint16_t size = 0; if (!read_u16(size) || remaining() < size) { return false; } const auto* first = reinterpret_cast<const char*>( bytes_.data() + position_); value.assign(first, first + size); position_ += size; return true; } [[nodiscard]] std::size_t remaining() const noexcept { return bytes_.size() - position_; } [[nodiscard]] std::span<const std::byte> unread() const noexcept { return bytes_.subspan(position_); } private: static std::uint8_t to_u8(std::byte value) noexcept { return std::to_integer<std::uint8_t>(value); } std::span<const std::byte> bytes_; std::size_t position_ = 0; }; } // namespace wire namespace protocol { struct Login { std::string username; }; struct Logout { std::uint32_t user_id = 0; }; struct Heartbeat { std::uint64_t timestamp = 0; }; template<class T> struct message_traits; template<> struct message_traits<Login> { static constexpr std::uint16_t command = 1; }; template<> struct message_traits<Logout> { static constexpr std::uint16_t command = 2; }; template<> struct message_traits<Heartbeat> { static constexpr std::uint16_t command = 3; }; template<class T> concept MessageType = requires { { message_traits<std::remove_cvref_t<T>>::command } -> std::convertible_to<std::uint16_t>; }; void encode_payload(wire::Writer& writer, const Login& message) { writer.write_string(message.username); } void encode_payload(wire::Writer& writer, const Logout& message) { writer.write_u32(message.user_id); } void encode_payload(wire::Writer& writer, const Heartbeat& message) { writer.write_u64(message.timestamp); } bool decode_payload(wire::Reader& reader, Login& message) { return reader.read_string(message.username); } bool decode_payload(wire::Reader& reader, Logout& message) { return reader.read_u32(message.user_id); } bool decode_payload(wire::Reader& reader, Heartbeat& message) { return reader.read_u64(message.timestamp); } using Message = std::variant<Login, Logout, Heartbeat>; enum class DecodeErrc { frame_too_small, payload_too_large, length_mismatch, unknown_command, invalid_payload }; struct DecodeError { DecodeErrc code; std::string_view description; }; using DecodeResult = std::variant<Message, DecodeError>; template<class Variant> class FrameDecoder; template<class... Messages> class FrameDecoder<std::variant<Messages...>> { static_assert((MessageType<Messages> && ...)); public: using message_type = std::variant<Messages...>; using result_type = std::variant<message_type, DecodeError>; static result_type decode(std::span<const std::byte> frame) { wire::Reader reader{frame}; std::uint16_t command = 0; std::uint32_t payload_size = 0; if (!reader.read_u16(command) || !reader.read_u32(payload_size)) { return DecodeError{DecodeErrc::frame_too_small, "frame header is incomplete"}; } if (payload_size > wire::max_payload_size) { return DecodeError{DecodeErrc::payload_too_large, "payload exceeds configured limit"}; } if (reader.remaining() != payload_size) { return DecodeError{DecodeErrc::length_mismatch, "payload length does not match frame"}; } result_type result = DecodeError{DecodeErrc::unknown_command, "unknown command"}; bool matched = false; ([&] { if (!matched && command == message_traits<Messages>::command) { matched = true; result = decode_one<Messages>(reader.unread()); } }(), ...); return result; } private: template<class MessageT> static result_type decode_one(std::span<const std::byte> payload) { wire::Reader reader{payload}; MessageT message{}; if (!decode_payload(reader, message) || reader.remaining() != 0) { return DecodeError{DecodeErrc::invalid_payload, "payload fields are invalid"}; } return message_type{ std::in_place_type<MessageT>, std::move(message)}; } }; } // namespace protocol namespace api::detail { void encode_payload() = delete; template<class T> concept AdlPayloadEncodable = requires( wire::Writer& writer, const T& message) { { encode_payload(writer, message) } -> std::same_as<void>; }; template<class T> void call_encode_payload(wire::Writer& writer, const T& message) { encode_payload(writer, message); } } // namespace api::detail namespace api { struct encode_frame_fn { template<protocol::MessageType Message> requires detail::AdlPayloadEncodable<Message> std::vector<std::byte> operator()(const Message& message) const { wire::Writer payload; detail::call_encode_payload(payload, message); if (payload.bytes().size() > wire::max_payload_size) { throw std::length_error{"payload exceeds configured limit"}; } wire::Writer frame; frame.write_u16(protocol::message_traits<Message>::command); frame.write_u32(static_cast<std::uint32_t>(payload.bytes().size())); frame.append(payload.bytes()); return std::move(frame).release(); } }; inline constexpr encode_frame_fn encode_frame{}; } // namespace api template<class Variant, class... Handlers> class Dispatcher; template<class... Messages, class... Handlers> class Dispatcher<std::variant<Messages...>, Handlers...> { public: explicit Dispatcher(Handlers... handlers) : handlers_(std::move(handlers)...) {} void dispatch(const std::variant<Messages...>& message) { std::visit( [this](const auto& concrete_message) { dispatch_one(concrete_message); }, message); } private: template<class Message> static consteval std::size_t handler_count() { return (std::size_t{ std::invocable<Handlers&, const Message&>} + ...); } static_assert(((handler_count<Messages>() == 1) && ...), "every message type must have exactly one handler"); template<class Handler, class Message> static void invoke_if_possible(Handler& handler, const Message& message) { if constexpr (std::invocable<Handler&, const Message&>) { std::invoke(handler, message); } } template<class Message> void dispatch_one(const Message& message) { std::apply( [&](auto&... handlers) { (invoke_if_possible(handlers, message), ...); }, handlers_); } std::tuple<Handlers...> handlers_; }; template<class Variant, class... Handlers> auto make_dispatcher(Handlers&&... handlers) { return Dispatcher<Variant, std::decay_t<Handlers>...>{ std::forward<Handlers>(handlers)...}; } int main() { auto dispatcher = make_dispatcher<protocol::Message>( [](const protocol::Login& message) { std::cout << "login: " << message.username << '\n'; }, [](const protocol::Logout& message) { std::cout << "logout: " << message.user_id << '\n'; }, [](const protocol::Heartbeat& message) { std::cout << "heartbeat: " << message.timestamp << '\n'; }); const auto frame = api::encode_frame(protocol::Login{"Aet"}); auto result = protocol::FrameDecoder<protocol::Message>::decode(frame); std::visit( [&](auto&& value) { using T = std::remove_cvref_t<decltype(value)>; if constexpr (std::same_as<T, protocol::Message>) { dispatcher.dispatch(value); } else { std::cout << "decode error: " << value.description << '\n'; } }, result); } |
声明:本文为原创文章,版权归Aet所有,欢迎分享本文,转载请保留出处!
你可能也喜欢
- ♥ C++_ 引用折叠、万能引用、完美转发、auto推导、函数指针引用、顶层const、底层const04/30
- ♥ Deelx正则引擎使用12/24
- ♥ C++_PIMPL 模式07/13
- ♥ 编译器扩展语法:一07/06
- ♥ C++11_四种类型转换11/10
- ♥ C++20_第二篇03/21