Menu
CoddyTech

optional, variant, any

Thuộc phần Lập trình hướng đối tượng trong hành trình C++ của Coddy. Bài học 89 trên 104.

C++ hiện đại cung cấp ba kiểu từ vựng trong các header <optional>, <variant> và <any>, giúp bạn xử lý các giá trị có thể không tồn tại, chứa một trong nhiều kiểu dữ liệu hoặc lưu trữ bất kỳ kiểu dữ liệu nào.

std::optional<T> biểu diễn một giá trị có thể tồn tại hoặc không, rất phù hợp với các hàm có thể thất bại mà không ném ngoại lệ:

#include <iostream>
#include <optional>
std::optional<int> findIndex(const std::string& str, char c) {
    for (size_t i = 0; i < str.size(); ++i) {
        if (str[i] == c) return i;
    }
    return std::nullopt;  // Không có giá trị
}
int main() {
    auto result = findIndex("hello", 'l');
    if (result.has_value()) {
        std::cout << "Found at: " << *result << "\n";  // 2
    }
}

std::variant<Types...> là một union an toàn về kiểu, luôn chứa chính xác một trong các kiểu đã chỉ định:

#include <iostream>
#include <variant>
#include <string>
int main() {
    std::variant<int, double, std::string> data;
    data = 42;
    std::cout << std::get<int>(data) << "\n";
    data = "hello";
    if (std::holds_alternative<std::string>(data)) {
        std::cout << std::get<std::string>(data) << "\n";
    }
}

std::any có thể chứa giá trị thuộc bất kỳ kiểu dữ liệu nào, được xác định khi chạy chương trình. Dùng std::any_cast để lấy giá trị:

#include <iostream>
#include <any>
int main() {
    std::any value = 10;
    std::cout << std::any_cast<int>(value) << "\n";
    value = std::string("text");
    std::cout << std::any_cast<std::string>(value) << "\n";
}

Chọn std::optional cho các giá trị có thể là null, std::variant khi bạn biết các kiểu dữ liệu có thể có tại thời điểm biên dịch, và chỉ dùng std::any khi bạn thực sự cần sự linh hoạt về kiểu dữ liệu tại thời điểm chạy.

challenge icon

Thử thách

Dễ

Hãy xây dựng một hệ thống cấu hình để minh họa sức mạnh của các kiểu từ vựng hiện đại trong C++. Bạn sẽ tạo một trình quản lý cài đặt linh hoạt, có thể xử lý các giá trị có thể không tồn tại, có thể chứa nhiều kiểu dữ liệu khác nhau hoặc cần tính linh hoạt về kiểu dữ liệu khi chạy chương trình.

Bạn sẽ sắp xếp code trong ba tệp:

  • ConfigTypes.h: Định nghĩa các kiểu giá trị cấu hình và các hàm hỗ trợ.

    Tạo hàm (function) tên là parseValue, nhận một chuỗi và thử phân tích chuỗi đó thành số nguyên. Nếu chuỗi biểu diễn một số nguyên hợp lệ, hãy trả về số nguyên được bọc trong std::optional. Nếu phân tích thất bại (chuỗi chứa ký tự không phải số), hãy trả về std::nullopt. Để đơn giản, hãy xem một chuỗi là hợp lệ nếu chuỗi chỉ chứa các chữ số (và có thể có dấu trừ ở đầu).

    Đồng thời, tạo bí danh kiểu tên là Setting bằng std::variant, có thể chứa một trong các kiểu int, double hoặc std::string.

    Cuối cùng, tạo hàm tên là describeSetting, nhận một Setting và trả về chuỗi mô tả kiểu dữ liệu mà giá trị đó chứa cùng giá trị của nó theo định dạng sau:

    • Với int: Integer: [value]
    • Với double: Double: [value]
    • Với string: String: [value]
  • DynamicStore.h: Tạo một kho khóa giá trị đơn giản bằng std::any.

    Định nghĩa lớp DynamicStore có thể lưu trữ giá trị thuộc bất kỳ kiểu nào. Lớp này cần có:

    • Phương thức set nhận một khóa dạng chuỗi và một giá trị std::any, rồi lưu chúng bên trong (dùng std::map)
    • Phương thức get nhận một khóa và trả về giá trị std::any (trả về một std::any rỗng nếu khóa không tồn tại)
    • Phương thức hasKey trả về true nếu khóa tồn tại
  • main.cpp: Đọc ba đầu vào:
    1. Một chuỗi có thể là một số (để kiểm tra std::optional)
    2. Một ký hiệu kiểu dữ liệu: int, double hoặc string
    3. Một giá trị tương ứng với kiểu dữ liệu đó

    Minh họa cả ba kiểu từ vựng:

    Đầu tiên, dùng hàm parseValue với đầu vào thứ nhất. Nếu chuỗi chứa một giá trị, hãy in ra Parsed: [value]. Nếu chuỗi rỗng, hãy in ra Parse failed.

    Thứ hai, tạo một biến thể Setting dựa trên ký hiệu kiểu dữ liệu. Nếu kiểu dữ liệu là int, hãy lưu giá trị dưới dạng số nguyên. Nếu là double, hãy lưu giá trị dưới dạng số thực. Nếu là string, hãy lưu giá trị dưới dạng chuỗi. Sau đó, gọi describeSetting và in ra kết quả.

    Thứ ba, tạo một DynamicStore, lưu chuỗi "config_loaded" dưới khóa "status", sau đó truy xuất chuỗi đó và in ra: Status: [value]. Dùng std::any_cast<std::string> để lấy giá trị.

Ví dụ, với các đầu vào 42, int và 100:

Parsed: 42
Integer: 100
Status: config_loaded

Với các đầu vào hello, double và 3.14:

Parse failed
Double: 3.14
Status: config_loaded

Với các đầu vào -15, string và username:

Parsed: -15
String: username
Status: config_loaded

Nhớ thêm các thư viện phù hợp: <optional>, <variant>, <any>, <string> và <map> khi cần. Dùng std::holds_alternative và std::get để thao tác với biến thể, và has_value() hoặc chuyển đổi trực tiếp sang boolean để kiểm tra đối tượng optional.

Tự làm thử

#include <iostream>
#include <string>
#include <any>
#include "ConfigTypes.h"
#include "DynamicStore.h"

int main() {
    // Đọc ba đầu vào
    std::string input1;  // Chuỗi có thể là số (để kiểm tra std::optional)
    std::string typeIndicator;  // Biến chỉ kiểu: int, double hoặc string
    std::string value;  // Giá trị tương ứng với kiểu đó
    
    std::cin >> input1;
    std::cin >> typeIndicator;
    std::cin >> value;
    
    // TODO: Phần 1: Kiểm tra std::optional bằng parseValue
    // Dùng hàm parseValue với input1
    // Nếu có giá trị, in ra "Parsed: [value]"
    // Nếu rỗng, in ra "Parse failed"
    
    
    // TODO: Phần 2: Kiểm tra std::variant với Setting
    // Tạo một biến thể Setting dựa trên typeIndicator
    // Nếu kiểu là "int", lưu giá trị dưới dạng số nguyên
    // Nếu kiểu là "double", lưu giá trị dưới dạng số thực
    // Nếu kiểu là "string", lưu giá trị dưới dạng chuỗi
    // Sau đó gọi describeSetting và in kết quả ra
    
    
    // TODO: Phần 3: Kiểm tra std::any với DynamicStore
    // Tạo một DynamicStore
    // Lưu chuỗi "config_loaded" với khóa "status"
    // Lấy giá trị đó và in ra: "Status: [value]"
    // Dùng std::any_cast<std::string> để lấy giá trị
    
    
    return 0;
}
quiz iconTự kiểm tra

Bài học này có một bài trắc nghiệm ngắn. Bắt đầu bài học để trả lời và theo dõi tiến độ.

Tất cả bài học trong Lập trình hướng đối tượng

Tự luyện tập: Trình biên dịch C++ online