Những tính năng quan trọng trong C++11

C++11 là một phiên bản cải tiến và nâng cấp từ C++98 (hay các bạn vẫn gọi là C++), với những tính năng mới tối ưu hơn, dễ sử dụng hơn, dễ quản lý bộ nhớ hơn và khắc phục được các nhược điểm của phiên bản C++98. Những cải tiến quan trọng đó bao gồm các tính năng thú vị sau đây.

1. Khai báo biến với từ khoá auto

Bình thường, trong C++98, khi khai báo biến buộc phải chỉ rõ kiểu dữ liệu của biến, chẳng hạn:

Trong C++11, ta có thể yêu cầu chương trình dịch tự xác định kiểu dữ liệu của biến thông qua giá trị ban đầu của biến bằng cách khai báo biến với từ khoá auto, đoạn mã trên có thể viết lại như sau:

Khai báo biến với từ khoá auto giúp tiết kiệm được kha khá mã nguồn nhưng vẫn dễ đọc:

Lưu ý là C++11 hiểu được cả >>

Khi sử dụng auto cho kết quả trả lại của hàm, phải chỉ rõ kiểu kết quả trả lại của hàm:

2. Con trỏ std::nullptr

Trong C++98 giá trị vô nghĩa của biến con trỏ là NULL, NULL là số nguyên: 0. Sự đồng nhất NULL và 0 có điểm bất tiện. Trong ví dụ dưới đây, khi gọi foo(NULL) thì hàm func nào sẽ được gọi?

Câu trả lời là foo(int) chứ không phải là foo(int*) vì NULL là một số nguyên.

C++11 định nghĩa rõ ràng giá trị vô nghĩa của con trỏ là nullptr. Trong ví dụ trên, nếu gọi foo(nullptr) thì foo(int*) sẽ được gọi.
nullptr được sử dụng tương tự như NULL, ngoại trừ một điểm: nullptr thuộc kiểu con trỏ. Giá trị NULL trong C++11 vẫn được sử dụng để tương thích ngược với C++98 các phiên bản trước. Ví dụ sau minh hoạ cách sử dụng nullptr:

3. Cấu trúc lặp for duyệt qua các phần tử của một tập hợp (Ranged-base for loop)

C++11 bổ sung cấu trúc lặp for dùng để duyệt qua các phần tử của một tập hợp, tương tự như thuật toán for_each trong thư viện algorithms của C++98 nhưng thuận tiện hơn nhiều:

Tương tự:

4. Sử dụng chỉ định override và final cho các hàm ảo

Trong chương trình sau, ở định nghĩa lớp B, người lập trình muốn định nghĩa lại (override) hàm ảo Show() của lớp cha: A:

Nếu chương trình đúng thì p->Show() phải gọi đến B::Show(), nhưng chương trình trên gọi đến A::Show(). Nguyên nhân do hàm Show trong lớp B khai báo khác với nguyên mẫu hàm Show trong lớp A (có/không có const). Theo đó, Show trong lớp B chỉ được coi là định nghĩa chồng tên (overload) và là hàm khác với Show của lớp A. Đây là một lỗi trong lập trình. Để đạt mục đích định nghĩa lại, phải sửa: bỏ const đi.
Nhằm tránh những lỗi như trên và để rõ ràng hơn trong mã nguồn, C++11 bổ sung chỉ định override để chỉ rõ hàm ảo là hàm định nghĩa lại.

Trong ví dụ sau, nếu có chỉ định override cho hàm Show, chương trình dịch có thể bắt lỗi được ngay:

C++11 cũng bổ sung thêm chỉ định final cho hàm ảo, khi đó hàm ảo này ở các lớp con sẽ không được phép định nghĩa lại. Ví dụ:

5. Kiểu dữ liệu liệt kê enum class

Trong C++98, cùng một phạm vi mã nguồn (scope), các giá trị trong 2 kiểu dữ liệu liệt kê (enum) phải khác nhau, chẳng hạn:

C++11 khắc phục hạn chế nói trên bằng kiểu liệt kê enum class. Enum class hỗ trợ định kiểu mạnh: chỉ rõ giá trị liệt kê thuộc kiểu liệt kê nào, do đó tránh được xung đột tên:

6. Con trỏ thông minh (smart pointers)

C++11 định nghĩa một số kiểu dữ liệu “con trỏ thông minh” trong thư viện memory nhằm quản lý bộ nhớ cấp phát động tốt hơn so với việc sử dụng kiểu dữ liệu con trỏ truyền thống.
Có ba loại con trỏ thông minh:
– shared_ptr: Vùng nhớ do một con trỏ shared_ptr trỏ đến có thể cùng được trỏ bởi nhiều con trỏ shared_ptr khác. Vùng nhớ do con trỏ shared_ptr trỏ đến được quản lý theo cơ chế đếm tham chiếu (đếm số con trỏ trỏ tới). Khi số tham chiếu bằng 0, vùng nhớ được giải phóng tự động.
– weak_ptr: Con trỏ weak_ptr trỏ tới vùng nhớ trỏ bởi shared_ptr nhưng không làm tăng số tham chiếu tới vùng nhớ, do đó không ảnh hưởng đến vòng đời của vùng nhớ.
– unique_ptr: Vùng nhớ do con trỏ unique_ptr trỏ tới không được đồng thời trỏ bởi các con trỏ khác. Vùng nhớ trỏ bởi con trỏ unique_ptr cũng được tự động giải phóng khi không còn con trỏ nào trỏ tới.

Sử dụng shared_ptr và weak_ptr:

Sử dụng hàm make_shared thay cho new cấp phát bộ nhớ cho con trỏ shared_ptr:

Trong ví dụ trên make_shared hiệu quả hơn do cấp phát một vùng nhớ cho cả p và đối tượng thuộc lớp A, trong khi new phải dùng đến 2 lần cấp phát. Ở lệnh gọi foo, khi bar() gây ra lỗi make_shared(10) vẫn đảm bảo giải phóng được vùng nhớ không gây rò rỉ bộ nhớ như khi sử dụng new A(10).

Sử dụng weak_ptr giải quyết vấn đề tham chiếu vòng của shared_ptr:

Khi thoát khỏi hàm foo, biến cục bộ root bị huỷ nhưng số tham chiếu đến vùng nhớ do root trỏ đến vẫn còn lại 1, vì vậy vùng nhớ không được giải phóng.

Để khắc phục, có thể “bẻ gẫy” tham chiếu vòng bằng weak_ptr như sau:

Sử dụng con trỏ unique_ptr:

7. Hàm lambda

Ở ví dụ sau, cần phải định nghĩa 2 hàm cmp và print để phục vụ cho thao tác sort và for_each:

C++11 cung cấp cú pháp viết các hàm lamda, trong ví dụ trên, thay vì phải định nghĩa cmp và print chỉ cần sử dụng hàm lamda như sau:

Sử dụng các biến bên ngoài trong hàm lambda:

Hàm lambda không có tên, trong trường hợp muốn tái sử dụng một hàm lamda đã viết, cần lưu trữ hàm trong biến thuộc kiểu std::function

8. Các hàm std::begin và std::end

C++11 cung cấp 2 hàm std::begin và std::end tự do (không phải hàm thành phần, member function, của một đối lớp nào). begin(T) và end(T) trả lại giá trị tương ứng của T.begin() và T.end() hoặc con trỏ tới phần tử đầu và sau phần tử cuối đối với một mảng tĩnh (array[]):

Sử dụng begin, end làm tăng tính tổng quát (generic) của chương trình

9. static_assert và thư viện type_traits

Sử dụng static_assert để kiểm tra điều kiện tham số của một định nghĩa template trong thời gian dịch (compile-time):

Kết hợp static_assert và thư viện type_traits trong kiểm tra kiểu dữ liệu trong thời gian dịch:

Hy vọng bài chia sẻ này sẽ giúp ích cho các bạn!

Nhận xét