Tối ưu hóa C++ với `constexpr`: Tính toán tại thời điểm biên dịch

Trong C++, từ khóa constexpr cho phép bạn khai báo rõ ràng các hàm hoặc hàm tạo đối tượng mà trình biên dịch có thể đánh giá như các biểu thức hằng số (constant expressions) tại thời điểm biên dịch. Điều này giúp trình biên dịch xác minh rằng biểu thức được khai báo thực sự là một biểu thức hằng số và có thể được tính toán trước khi chương trình chạy.

Các biến được khai báo là constexpr luôn là biến const và phải được khởi tạo bằng một biểu thức hằng số:


constexpr int soLuongCoDinh = 20;  // 20 là biểu thức hằng số
constexpr int gioiHan = soLuongCoDinh + 1; // soLuongCoDinh + 1 là biểu thức hằng số
constexpr int kichThuoc = hamKichThuoc(); // Khai báo hợp lệ nếu hamKichThuoc là một hàm constexpr

Cần lưu ý sự khác biệt giữa constexprconst khi áp dụng cho con trỏ. constexpr chỉ áp dụng cho chính con trỏ, không phải đối tượng mà con trỏ trỏ tới:


const int* conTroDenHangSo = nullptr;        // conTroDenHangSo là một con trỏ tới một số nguyên không đổi
constexpr int* conTroHangSo = nullptr;   // conTroHangSo là một con trỏ không đổi tới một số nguyên

Dưới đây là một ví dụ cụ thể hơn:


#include <iostream>
#define DO_TINH_TOAN 10

int hamTinhToan() {
    int i = 2;
    return i;
}

constexpr int hamTinhToanConstexpr() {
    return 5;
}

constexpr int fibonacci(const int n) {
    // Phiên bản đệ quy của hàm Fibonacci, yêu cầu C++11 hoặc mới hơn
    return n == 1 || n == 2 ? 1 : fibonacci(n - 1) + fibonacci(n - 2);
}

int main() {
    char mang1[10];                      // Hợp lệ
    char mang2[DO_TINH_TOAN];            // Hợp lệ

    int doDai = 10;
    // char mang3[doDai];                  // Không hợp lệ vì doDai không phải biểu thức hằng số
    
    const int doDai2 = doDai + 1;
    constexpr int doDai2Constexpr = 1 + 2 + 3;
    // char mang4[doDai2];                // Không hợp lệ vì doDai2 không phải biểu thức hằng số
    char mang4[doDai2Constexpr];         // Hợp lệ vì doDai2Constexpr là biểu thức hằng số
    
    // char mang5[hamTinhToan() + 5];          // Không hợp lệ vì hamTinhToan() không phải là constexpr
    char mang6[hamTinhToanConstexpr() + 1]; // Hợp lệ vì hamTinhToanConstexpr() là constexpr
    
    std::cout << "Fibonacci(10): " << fibonacci(10) << std::endl; 
    // Kết quả mong đợi: 1, 1, 2, 3, 5, 8, 13, 21, 34, 55
    return 0;
}

Trong ví dụ trên, doDai2 mặc dù được khai báo là const nhưng không thể dùng để khởi tạo kích thước mảng. Theo tiêu chuẩn C++, kích thước mảng phải là một biểu thức hằng số, không chỉ là một hằng số thông thường. Nếu không phải biểu thức hằng số, hành vi sẽ không xác định (undefined behavior).

Hơn nữa, các hàm được đánh dấu constexpr có thể sử dụng đệ quy. Kể từ C++14, bạn có thể sử dụng các câu lệnh đơn giản như biến cục bộ, vòng lặp và câu lệnh điều kiện bên trong các hàm constexpr. C++17 giới thiệu if constexpr, cho phép loại bỏ các nhánh không cần thiết của một mẫu (template) tại thời điểm biên dịch dựa trên điều kiện.

Xem xét ví dụ với if constexpr:


#include <iostream>
#include <type_traits>

template<typename T>
auto xuatGiaTri(T giaTri) {
    // if constexpr chỉ được đánh giá tại thời điểm biên dịch
    if constexpr (std::is_same_v<T, char*>) {
        std::cout << "Doi voi char*: " << *giaTri << std::endl;
    }
    else {
        std::cout << "Doi voi loai khac: " << giaTri << std::endl;
    }
}

int main() {
    char* ptrChar = new char('a');
    int soNguyen = 1;

    xuatGiaTri(soNguyen); // Tương đương với std::cout << giaTri << std::endl;
    xuatGiaTri(ptrChar);  // Tương đương với std::cout << *giaTri << std::endl;

    delete ptrChar; // Giải phóng bộ nhớ đã cấp phát
    return 0;
}
// Kết quả đầu ra:
// Doi voi loai khac: 1
// Doi voi char*: a

Trong ví dụ trên, khi gọi xuatGiaTri(soNguyen), hàm xuatGiaTri sẽ được nội suy (instantiated) thành nhánh else. Tương tự, khi gọi xuatGiaTri(ptrChar), nó sẽ được nội suy thành nhánh if. Việc sử dụng if constexpr một cách khéo léo có thể giúp giảm bớt nhu cầu về các mẫu chuyên biệt hóa (template specialization) và do đó làm giảm lượng mã nguồn.

Thẻ: C++ constexpr compile-time computation constant expression template metaprogramming

Đăng vào ngày 18 tháng 6 lúc 05:23