C++ cung cấp nhiều công cụ để định nghĩa kiểu dữ liệu tùy chỉnh, trong đó Union và Enum là hai loại quan trọng. Bài viết này sẽ khám phá chi tiết cách sử dụng chúng cũng như các cải tiến từ C++11 trở lên.
1. Enum (Liệt kê)
Enum cho phép định nghĩa một tập hợp các hằng số nguyên có tên, giúp code dễ đọc và an toàn hơn về kiểu.
1.1 Enum truyền thống vs enum class (khuyên dùng enum class)
// Enum truyền thống (không có namespace riêng)
enum Direction { Up, Down, Left, Right };
// Enum class (có phạm vi, C++11)
enum class Result : int {
OK = 0,
Error = 1,
Pending = 2
};
So sánh chi tiết:
| Tính năng | Enum truyền thống | Enum class |
|---|---|---|
| Phạm vi | Ảnh hưởng ra bên ngoài | Giới hạn trong tên enum |
| Chuyển đổi kiểu | Ngầm định sang int | Không cho phép ngầm định |
| Kiểu cơ sở | Mặc định int | Có thể chỉ định (int, char, uint64_t...) |
| Khai báo trước | Khó khăn | Dễ dàng (cần chỉ định kiểu) |
| Tương thích STL | Hạn chế | Tốt |
Cách viết khuyên dùng:
enum class Priority : uint8_t {
Low = 0,
Medium,
High,
Critical
};
// Sử dụng
Priority p = Priority::High;
// int x = p; // Lỗi biên dịch!
// int y = static_cast<int>(p); // Đúng: chuyển đổi tường minh
// Enum class kết hợp switch rất tiện
switch (p) {
case Priority::Low: // xử lý... break;
case Priority::Medium: // xử lý... break;
case Priority::High:
case Priority::Critical:
// xử lý khẩn cấp
break;
}
1.2 Kỹ thuật nâng cao
- Kiểm tra kiểu cơ sở:
std::underlying_type_t<Priority> - Tạo alias với using để code ngắn gọn hơn
- Bit flags với enum class và operator overloading
enum class FileMode : unsigned {
Read = 1 << 0, // 1
Write = 1 << 1, // 2
Exec = 1 << 2, // 4
All = Read | Write | Exec
};
constexpr FileMode operator|(FileMode a, FileMode b) {
return static_cast<FileMode>(
static_cast<unsigned>(a) | static_cast<unsigned>(b));
}
// Sử dụng
FileMode mode = FileMode::Read | FileMode::Write;
2. Union
Union là kiểu dữ liệu cho phép tiết kiệm bộ nhớ bằng cách chia sẻ vùng nhớ giữa các thành viên.
2.1 Cách dùng cơ bản
union Payload {
int integer;
float decimal;
char buffer[8];
};
Payload p;
p.integer = 100;
// sizeof(Payload) == 8 (tùy platform)
Quy tắc quan trọng:
- Chỉ một thành viên được active tại một thời điểm.
- Đọc thành viên không active là undefined behavior (UB).
- C++11 trở về trước: không chứa thành viên có constructor/destructor phức tạp.
- C++11 trở lên: cho phép nhưng phải quản lý vòng đời thủ công.
2.2 Anonymous Union
Thường dùng trong struct để tiết kiệm bộ nhớ:
struct Message {
int type;
union { // Anonymous union
int id;
double price;
std::string text; // C++11+: cần quản lý thủ công
};
};
2.3 Union với thành viên phức tạp
union Holder {
int number;
std::string text; // Có constructor
Holder() : number(0) {} // Khởi tạo int
Holder(const std::string& s) : text(s) {}
~Holder() { if (which == 2) text.~basic_string(); }
int which = 1; // Theo dõi thành viên active
};
Khuyên: Tránh viết union thủ công kiểu này, hãy dùng std::variant.
3. std::variant - Union an toàn của C++17
#include <variant>
#include <string>
using Value = std::variant<int, double, std::string>;
Value v = 42; // int
v = 3.14; // double
v = "hello"; // string
// Cách 1: dùng get_if
if (auto* p = std::get_if<int>(&v)) {
std::cout << *p << '\n';
}
// Cách 2: dùng visit (khuyên dùng)
std::visit([](auto&& arg) {
using T = std::decay_t<decltype(arg)>;
if constexpr (std::is_same_v<T, int>) {
std::cout << "Integer: " << arg << '\n';
}
else if constexpr (std::is_same_v<T, double>) {
std::cout << "Double: " << arg << '\n';
}
else if constexpr (std::is_same_v<T, std::string>) {
std::cout << "String: " << arg << '\n';
}
}, v);
Ưu điểm:
- An toàn về kiểu, không UB
- Tự động quản lý vòng đời
- Hỗ trợ
std::monostatecho trường hợp không có giá trị - Chứa được mọi kiểu (kể cả kiểu phức tạp)
Hiệu năng: Gần như tương đương union thủ công nhưng an toàn hơn nhiều.
4. Hướng dẫn thực hành
- Enum:
- Luôn ưu tiên enum class trong code mới.
- Chỉ dùng enum truyền thống khi tương thích với C.
- Chỉ định kiểu cơ sở phù hợp (
uint8_t,uint32_t...).
- Union:
- Kiểu đơn giản (POD) có thể dùng trực tiếp.
- Kiểu phức tạp → dùng
std::variant. - Chỉ dùng union với quản lý thủ công khi cần hiệu năng cực cao.
- Kết hợp:
enum class DataType { Integer, Float, Text };
struct Any {
DataType tag;
std::variant<int, double, std::string> value;
};
- C++20: Dùng
[[no_unique_address]]để tối ưu hóa bộ nhớ trong các kiểu phức tạp.
5. Lỗi thường gặp
- Đọc thành viên không active của union → UB (crash hoặc giá trị rác).
- Quên static_cast khi dùng enum class (đây là tính năng, không phải bug).
- Chứa reference trong std::variant (không được, dùng
std::reference_wrapper). - Cross-platform: chú ý kiểu cơ sở của enum và alignment của union.
Khi nắm vững enum class và std->variant, code C++ sẽ trở nên an toàn hơn, mạnh mẽ hơn và hiện đại hơn. Union và enum truyền thống chỉ còn cần thiết khi làm việc với legacy code, hệ thống nhúng hoặc driver.