Khi phải viết một class có nhiều hàm constructors (thật ra chúng ta sẽ thường xuyên phải làm vậy), chúng ta phải chỉ định các giá trị mặc định cho tất cả các biến thành viên trong mỗi hàm constructor, điều này sẽ tạo ra các đoạn code dư thừa. Nếu bạn muốn cập nhật giá trị mặc định cho một biến thành viên, bạn sẽ phải tiến hành sửa đổi trên từng cái constructor một.

Bắt đầu từ C++ 11, chúng ta đã có thể gán trực tiếp một giá trị khởi tạo mặc định cho các biến thành viên thông thường của class (Là các biến mà không sử dụng từ khóa static khi khai báo):

#include <iostream>
 
class Rectangle
{
private:
    double m_length{ 1.0 }; // m_length has a default value of 1.0
    double m_width{ 1.0 }; // m_width has a default value of 1.0
 
public:
    Rectangle()
    {
        // This constructor will use the default values above since they aren't overridden here
    }
 
    void print()
    {
        std::cout << "length: " << m_length << ", width: " << m_width << '\n';
    }
};
 
int main()
{
    Rectangle x{}; // x.m_length = 1.0, x.m_width = 1.0
    x.print();
 
    return 0;
}

Đoạn chương trình này sẽ in ra kết quả:

length: 1.0, width: 1.0

Quá trình khởi tạo các biến thành viên không phải static (non-static member) (còn được gọi là in-class member initializers, tức là khởi tạo các biến thành viên ở bên trong class) sẽ cung cấp các giá trị mặc định cho các biến thành viên của bạn, và các hàm constructor của bạn sẽ sử dụng những những giá trị này để khởi tạo cho các biến thành viên của class trong trường hợp mà các hàm constructors này không cung cấp các giá trị khởi tạo cho các biến thành viên của class (thông qua member initialization list – danh sách khởi tạo biến thành viên).

Tuy nhiên, cần lưu ý rằng các hàm constructors vẫn sẽ quyết định về việc những loại đối tượng nào có thể được tạo ra. Xét trường hợp sau:

#include <iostream>
 
class Rectangle
{
private:
    double m_length{ 1.0 };
    double m_width{ 1.0 };
 
public:
 
    // note: No default constructor provided in this example
 
    Rectangle(double length, double width)
        : m_length{ length },
          m_width{ width }
    {
        // m_length and m_width are initialized by the constructor (the default values aren't used)
    }
 
    void print()
    {
        std::cout << "length: " << m_length << ", width: " << m_width << '\n';
    }
 
};
 
int main()
{
    Rectangle x{}; // will not compile, no default constructor exists, even though members have default initialization values
 
    return 0;
}

Mặc dù chúng ta đã cung cấp các giá trị mặc định cho tất cả các biến thành viên của class Rectangle, nhưng không có hàm constructor mặc định nào được khai báo, nên chúng ta không thể tạo ra đối tượng Rectangle không có đối số.

Nếu một giá trị khởi tạo mặc định được cung cấp và hàm constructor khởi tạo biến thành viên thông qua member initializer list (danh sách khởi tạo biến thành viên), thì member initializer list này sẽ được ưu tiên hơn. Điều này được thể hiện trong ví dụ sau:

#include <iostream>
 
class Rectangle
{
private:
    double m_length{ 1.0 };
    double m_width{ 1.0 };
 
public:
 
    Rectangle(double length, double width)
        : m_length{ length },
          m_width{ width }
    {
        // m_length and m_width are initialized by the constructor (the default values aren't used)
    }
 
    Rectangle(double length)
        : m_length{ length }
    {
        // m_length is initialized by the constructor.
        // m_width's default value (1.0) is used.
    }
 
    void print()
    {
        std::cout << "length: " << m_length << ", width: " << m_width << '\n';
    }
 
};
 
int main()
{
    Rectangle x{ 2.0, 3.0 };
    x.print();
 
    Rectangle y{ 4.0 };
    y.print();
 
    return 0;
}

Kết quả in ra:

length: 2.0, width: 3.0
length: 4.0, width: 1.0

Lưu ý rằng việc khởi tạo các biến thành viên bằng cách sử dụng các phương pháp khởi tạo các biến thành viên không phải static (non-static member initialization) đòi hỏi ta phải hoặc là khởi tạo sử dụng dấu bằng, hoặc là khởi tạo sử dụng một cặp dấu ngoặc nhọn {} – Hay còn gọi là brace (uniform) initializer, tức là cú pháp khởi tạo đồng đều sử dụng cặp dấu ngoặc nhọn ở trong C++. Dạng thức khởi tạo trực tiếp sẽ không hoạt động trong trường hợp này. 

Quy tắc: Hãy ưu tiên sử dụng các phương pháp khởi tạo biến thành viên không phải static để cung cấp các giá trị mặc định cho các biến thành viên của bạn.

Đăng ký kênh youtube để ủng hộ Cafedev nha các bạn, Thanks you!