15 năm "tháo gỡ": Thư viện chuẩn C++ và những thiết kế sai lầm không thể xóa bỏ
Trong suốt 15 năm qua, ủy ban chuẩn hóa C++ đã liên tục đưa ra các tính năng mới để thay thế cho những thiết kế cũ bị coi là sai lầm trong thư viện chuẩn. Tuy nhiên, do ràng buộc về tính tương thích ABI, những "di sản" này vẫn tồn tại mãi mãi, biến thư viện chuẩn thành một bảo tàng mà các kỹ sư thực tế thường phải né tránh.

15 năm "tháo gỡ": Thư viện chuẩn C++ và những thiết kế sai lầm không thể xóa bỏ
Gần đây, bài viết của Sandor Dargo về std::copyable_function đã kết thúc với một bảng tham khảo nhanh. Trong số các trình bao bọc (wrapper) có thể gọi được, có một mục khiến bất kỳ kỹ sư C++ nào cũng phải khựng lại: std::function: Legacy. Avoid in new code (Di sản. Tránh sử dụng trong code mới).
std::function ra mắt cùng C++11. Ủy ban chuẩn hóa đã mất 15 năm để đưa ra các trình bao bọc thay thế nó. Mới nhất là std::copyable_function trong C++26. Lời khuyên không phải là "hãy dùng cái này khi cần một callable có thể sao chép", mà là "đừng dùng cái gốc nữa".
Đây không phải là trường hợp cá biệt. Thư viện chuẩn C++ đã tự "quay lùi" sửa sai các tính năng của chính mình kể từ khi C++11 mới ra mắt. Có khi sự quay lùi này mang tính hình thức (một văn bản chính thức, đánh dấu là lỗi thời, rồi xóa bỏ trong phiên bản sau). Có khi đó là lời khuyên ngầm mà mọi kỹ sư senior đều nói với junior ("đừng đụng vào cái đó, dùng cái kia thay thế"). Và đôi khi, lỗi sai không thể viết vào tiêu chuẩn để sửa vì bị khóa bởi sự tương thích ABI, nên nó vẫn nằm trong thư viện chuẩn như một mặc định mà mọi hướng dẫn đều dùng đến, còn các codebase sản xuất thì âm thầm thay thế.
Mức độ 1: Những sự quay lùi chính thức
Mỗi mục dưới đây đều chỉ đến một văn bản thực tế mà nhóm làm việc đã chấp nhận. Đây không phải là tranh luận, mà là những lời thừa nhận bằng văn bản.
Trường hợp lịch sử rõ ràng nhất là std::auto_ptr, con trỏ thông minh của C++98 với ngữ nghĩa "copy-as-move" đã làm hỏng code tổng quát và các container chuẩn. Nó bị đánh dấu lỗi thời trong C++11 và bị xóa trong C++17. Thay thế cho nó là std::unique_ptr và lambda.
Các đặc tả ngoại lệ động (dynamic exception specifications) như throw(X, Y) cũng bị loại bỏ tương tự, thay thế bằng noexcept. std::iterator, lớp cơ sở mà mọi sách "Effective C++" từng dạy bạn kế thừa, cũng bị đánh dấu lỗi thời và đề xuất xóa bỏ.
Điều đáng xấu hổ nhất là giao diện thu gom rác (garbage collection) của C++11. Ủy ban đã đưa ra std::declare_reachable và bạn bè, nhưng không một bản triển khai lớn nào từng cung cấp bộ thu gom rác thực sự đằng sau các điểm vào này. Giao diện này bị xóa trong C++23 sau 12 năm tồn tại mà chưa từng hoạt động như quảng cáo.
Mức độ 2: Những thứ "ai cũng biết phải tránh" nhưng chưa bị xóa
Những tính năng này vẫn còn trong tiêu chuẩn. Không cái nào bị đánh dấu lỗi thời chính thức. Nhưng mọi kỹ sư C++ kinh nghiệm đều sẽ bảo nhân viên mới tránh chúng ngay ngày đầu tiên đi làm.
std::regex ra mắt trong C++11. Chính văn bản P1844R1 của ủy ban ghi nhận rằng "hiệu suất của std::regex rất kém so với các giải pháp khác". Thư viện chuẩn đã đưa ra một tính năng mà chất lượng được ghi nhận chính thức là quá chậm để sử dụng. Trong thực tế, mọi người dùng Boost.Regex, RE2 hoặc CTRE.
std::async cũng vậy. Hàm hủy của future trả về sẽ chặn cho đến khi thao tác async hoàn thành, dẫn đến bẫy deadlock. Các codebase độ trễ thấp (low-latency) đều dùng thread pool hoặc các nguyên thủy async riêng biệt thay vì dùng cái này.
std::list là ví dụ kinh điển. Bjarne Stroustrup từng chứng minh std::vector đánh bại std::list ngay cả với khối lượng công việc "chèn vào giữa container lớn", vì việc quét tuyến tính chiếm ưu thế và việc truy con trỏ (pointer chase) trừng phạt bộ nhớ đệm (cache). std::list không bị đánh dấu lỗi thời, nhưng mọi kỹ sư đều được dạy không bao giờ dùng nó.
Mức độ 3: Những container ai cũng dùng nhưng không ai sửa được
Đây là mức độ nghiêm trọng nhất. Những container này là mặc định mà mọi sách giáo khoa dạy, nhưng ủy ban không thể sửa vì sợ phá vỡ ABI. Ba trong số đó đã được chứng minh là sai lầm so với tiêu chuẩn của các ngôn ngữ khác.
std::unordered_map là trường hợp điển hình. Đặc tả C++11 yêu cầu tính ổn định của bucket và iterator, cấm hiệu quả việc sử dụng "open addressing" – kiến trúc bảng băm thân thiện với cache mà mọi người khác đã chuẩn hóa trong 15 năm qua. Rust's HashMap dùng kiến trúc này mặc định và nhanh hơn nhiều. C++ thêm std::flat_map trong C++23 nhưng không thể sửa std::unordered_map.
std::map và std::set dùng cây đỏ-đen (red-black trees), cấp phát bộ nhớ cho mỗi nút và truy con trỏ liên tục. Cây B (B-trees) đã đánh bại cây đỏ-đen cho các container trong bộ nhớ từ lâu. Abseil, Boost và Rust đều dùng B-tree, nhưng C++ vẫn giữ cây đỏ-đen làm mặc định.
Một bài kiểm tra hiệu suất (benchmark) gần đây đã so sánh implementation ngây thơ của C++ (dùng unordered_map, map, list) và Rust (dùng HashMap, BTreeMap, VecDeque). Kết quả cho thấy độ trễ P99 của C++ cao hơn 58 lần so với Rust. Không phải vì ngôn ngữ Rust nhanh hơn, mà vì thư viện chuẩn của Rust chọn đúng mặc định ngay từ đầu, còn C++ bị mắc kẹt với những thiết kế cũ kỹ không thể sửa.
Vấn đề cốt lõi: Tại sao chuyện này cứ lặp lại?
Mô hình chung ở cả ba mức độ là giống nhau. Thư viện chuẩn C++ là một lớp tích tụ vĩnh viễn của các thiết kế mà lĩnh vực này sau đó nhận ra là sai. Ủy ban đôi khi thừa nhận lỗi bằng văn bản và đưa ra giấy tờ đánh dấu lỗi thời. Đôi khi lỗi quá phổ biến để có thể đánh dấu, nên tính năng mới được đưa ra song song và cộng đồng học bằng truyền miệng cái nào nên dùng.
Nhưng quan trọng hơn, cam kết cấu trúc của C++ là không bao giờ phá vỡ code hiện có. Điều này có nghĩa là các container chậm chạp, regex bị hỏng, async gây deadlock, tất cả đều phải ở lại. Thư viện chuẩn giờ giống như một chiếc bánh nhiều lớp của 15 năm lịch sử "đừng dùng cái đó, dùng cái này", và công việc của kỹ sư là phải nhớ ngày tháng của những lớp tồi tệ đó.
So sánh với các ngôn ngữ khác
Câu hỏi công bằng là: Ngôn ngữ nào cũng mắc sai lầm mà. Python xóa hơn 20 module thư viện chuẩn trong một PEP. Java xóa Nashorn, Applets. Rust dùng hệ thống "edition" để loại bỏ cú pháp cũ. C# thậm chí chấp nhận breaking changes khi chuyển từ .NET Framework sang .NET Core.
Sự khác biệt không phải là C++ mắc sai lầm, mà là tỷ lệ giữ lại (retention rate). Python xóa, Java xóa, Rust chỉnh sửa. C++ thì giữ lại gần như mọi thứ. Lý do là cấu trúc: P1863R1 "ABI - Now or Never" đã đặt câu hỏi cho ủy ban vào năm 2020: hoặc là chấp nhận phá vỡ ABI cho C++23, hoặc là cam kết ổn định ABI vĩnh viễn. Họ đã chọn ổn định vĩnh viễn.
Đó là lý do std::regex không thể sửa. Đó là lý do std::unordered_map không thể dùng open addressing. Đó là lý do những codebase C++ hiện đại tại các công ty công nghệ hàng đầu đều được xây dựng chủ yếu từ các thư viện không chuẩn như Boost, Abseil, Folly thay vì thư viện chuẩn.
Lời khuyên "std::function is legacy" là hoàn toàn chính xác. Điểm mấu chốt là câu nói đó đã 15 tuổi và vẫn đếm. Thư viện chuẩn C++ đã biến nó thành sự thật với nội dung của chính mình trong suốt kỷ nguyên sau C++11. Lần tới khi ủy ban đưa ra một tính năng mới lấp lánh để thay thế cái gì đó họ đưa ra một thập kỷ trước, cách đọc trung thực là: thiết kế cũ không hoạt động, và giờ đây có một văn bản nói về điều đó.
Bài viết liên quan

Công nghệ
Cerebras, đối tác thân thiết của OpenAI, sẵn sàng cho đợt IPO kỷ lục định giá tới 26,6 tỷ USD
04 tháng 5, 2026

Công nghệ
Anthropic và OpenAI mang cuộc chiến công nghệ sang chính trường Mỹ
20 tháng 5, 2026

Công nghệ
Cảnh sát bắt giữ nghi can được cho là "ông trùm" của trang web buôn bán ma túy Dream Market
14 tháng 5, 2026
