C++ Programming – Constructor member initializer lists


Trong bài học trước chúng ta đã được học cách thiết lập các biến member trong class thông qua phép gán constructor như sau:

class Something
{
private:
    int m_value1;
    double m_value2;
    char m_value3;
 
public:
    Something()
    {
        // These are all assignments, not initializations
        m_value1 = 1;
        m_value2 = 2.2;
        m_value3 = 'c';
    }
};

Khi constructor Somthing thực thi, thì các biến m_value1, m_value2, m_value3 được tạo ra và sau đó các biến này được thiết lập giá trị mặc định như trong code. Và đoạn code sau cũng có chức năng rất tương tự:

int m_value1;
double m_value2;
char m_value3;
 
m_value1 = 1;
m_value2 = 2.2;
m_value3 = 'c';

Cách trên đây là đúng, nhưng nó không được dùng nhiều trong C++.
Tuy nhiên, như bạn đã học trước, một số kiểu dữ liệu (như const và biến reference) phải được thiết lập giá trị ngay dòng khởi tạo biến.

class Something
{
private:
    const int m_value;
 
public:
    Something()
    {
        m_value = 1; // error: const vars can not be assigned to
    } 
};

Và đoạn đây là một cách tương tự như đoạn code trên:

const int m_value; // error: const vars must be initialized with a value
m_value = 5; //  error: const vars can not be assigned to

Vậy trong những trường hợp có biến hằng (const) hay biến tham chiếu (reference) thì chúng ta phải làm thế nào?

Member initializer lists

Để giải quyết vấn đề này, C++ cung cấp một phương thức để thiết lập các biến thông qua một danh sách (member initializer list), chúng ta có thể gọi như vậy.

Chúng ta có 3 phương pháp để thiết lập giá trị một biến trong C++:
copy, direct, uiniform (chỉ trong C++ 11). Và chúng ta rất thương xuyên dùng cách copy.

int value1 = 1; // copy initialization
double value2(2.2); // direct initialization
char value3 {'c'} // uniform initialization

Và chúng ta có thể hiểu initialization list giống như direct initialization hay iniform initialization.

Chúng ta cùng xem lại ví dụ đầu bài.

class Something
{
private:
    int m_value1;
    double m_value2;
    char m_value3;
 
public:
    Something()
    {
        // These are all assignments, not initializations
        m_value1 = 1;
        m_value2 = 2.2;
        m_value3 = 'c';
    }
};

Và bây giờ chúng ta sẽ chuyển ví dụ trên về phương pháp initialization list như đã đề câp:

class Something
{
private:
    int m_value1;
    double m_value2;
    char m_value3;
 
public:
    Something() : m_value1(1), m_value2(2.2), m_value3('c') // directly initialize our member variables
    {
    // No need for assignment here
    }
 
    void print()
    {
         std::cout << "Something(" << m_value1 << ", " << m_value2 << ", " << m_value3 << ")\n";
    }
};
 
int main()
{
    Something something;
    something.print();
    return 0;
}

Kết quả khi chạy chương trình trên:
Something(1, 2.2, c)

Như các bạn đã thấy thì danh sách các member được thêm trực tiếp sau constructor, nó bắt đầu với dấu “:” (colon), và sau đó mỗi biến được thiết lập trực tiếp và ngăn cách nhau bằng dấu “,” (comma).

Chú ý rằng chúng ta không cần có các phép gán trong phần thân của constructor. Chú ý rằng là không có dấu “;” (semicolon) ở cuối nhé.

Và đây là cách chúng ta phối hợp để tạo ra một constructor có tham số được nhập từ người lập trình.

#include <iostream>
 
class Something
{
private:
    int m_value1;
    double m_value2;
    char m_value3;
 
public:
    Something(int value1, double value2, char value3='c')
        : m_value1(value1), m_value2(value2), m_value3(value3) // directly initialize our member variables
    {
    // No need for assignment here
    }
 
    void print()
    {
         std::cout << "Something(" << m_value1 << ", " << m_value2 << ", " << m_value3 << ")\n";
    }
 
};
 
int main()
{
    Something something(1, 2.2); // value1 = 1, value2=2.2, value3 gets default value 'c'
    something.print();
    return 0;
}

Kết quả in ra như sau:
Something(1, 2.2, c)

Chú ý rằng bạn có thể sử dụng các thông số mặc định để cung cấp các giá trị mặc định trong trường hợp bạn không cung cấp các tham số cho constructor.

Và bây giờ chúng ta sẽ xem ví dụ về biến const.

class Something
{
private:
    const int m_value;
 
public:
    Something(): m_value(5) // directly initialize our const member variable
    {
    } 
};

Đoạn code trên không gây lỗi, vì compiler cho phép chúng ta thiết lập giá trị của biến const như thế, nhưn nếu các bạn gán thì sẽ gây lỗi.

Rule: Sử dụng member initializer list để thiết lập giá trị cho các member trong class thay vì dùng phép gán.

Uniform initialization in C++11

Trong C++11, thay vì dùng direct initialization, bạn cũng có thể dùng uniform initialization:

class Something
{
private:
    const int m_value;
 
public:
    Something(): m_value { 5 } // Uniformly initialize our member variables
    {
    } 
};

Và chúng tôi khuyến khích các bạn sử dụng cách này hơn, vì đây là một cú pháp mới (thậm chí nếu bạn không sử dụng const hay reference).

Rule: Nếu bạn sử dụng C++11 thì hãy ưu tiên dùng uniform initialiazion hơn là direct initializaion.

Initializing array members with member initializer lists

Xem xét một class với một mảng:

class Something
{
private:
    const int m_array[5];
 
};

Trước C++11, bạn chỉ có thể thiết lập các giá trị của mảng bằng 0 thông qua phương pháp initialization list.

class Something
{
private:
    const int m_array[5];
 
public:
    Something(): m_array {} // zero the member array
    {
        // If we want the array to have values, we'll have to use assignment here
    }
 
};

Tuy nhiên trong C++11, bạn hoàn toàn có thể thiết lập giá trị đầu cho các member trong mảng bằng cách sử dụng uniform initialization:

class Something
{
private:
    const int m_array[5];
 
public:
    Something(): m_array { 1, 2, 3, 4, 5 } // use uniform initialization to initialize our member array
    {
    }
 
};

Initializing member variables that are classes

Chúng ta hãy xem ví dụ sau:

#include <iostream>

class A
{
public:
    A(int x) { std::cout << "A " << x << "\n"; }
};
 
class B
{
private:
    A m_a;
public:
    B(int y)
         : m_a(y-1) // call A(int) constructor to initialize member m_a
    {
        std::cout << "B " << y << "\n";
    }
};
 
int main()
{
    B b(5);
    return 0;
}

Chương trình sẽ in ra:
A 4
B 5

Khi chúng ta tạo đối tượng b (dòng 23), constructor B(int) được gọi với giá trị truyền vào là 5. Trước khi phần thân (body) của B(int) chạy, thì m_a được thiết lập, lúc này gọi constructor A(int) với giá trị truyền vào là 4. Sau đó in ra “A 4“. Sau đó quay lại constructor B, và phần thân của B chạy, in ra “B 5“.

Formatting your initializer lists

C++ đưa cho chúng ta rất nhiều format, rất linh hoạt trong phương pháp initialization list.
Đây là cách dùng tất cả trong một dòng:

class Something
{
private:
    int m_value1;
    double m_value2;
    char m_value3;
 
public:
    Something() : m_value1(1), m_value2(2.2), m_value3('c') // everything on one line
    {
    }
};

Và đây là cách trên 2 dòng:

class Something
{
private:
    int m_value1;
    double m_value2;
    char m_value3;
 
public:
    Something(int value1, double value2, char value3='c') // this line already has a lot of stuff on it
        : m_value1(value1), m_value2(value2), m_value3(value3) // so we can put everything indented on next line
    {
    }
 
};

Và đây là cách chúng ta dùng nhiều dòng, rất linh hoạt:

class Something
{
private:
    int m_value1;
    double m_value2;
    char m_value3;
    float m_value4;
 
public:
    Something(int value1, double value2, char value3='c', float value4=34.6) // this line already has a lot of stuff on it
        : m_value1(value1), // one per line, commas at end of each line
        m_value2(value2),
        m_value3(value3),
        m_value4(value4) 
    {
    }
 
};

Vậy là chúng ta sẽ kết thúc phần constructor ở đây. Và chúng ta sẽ quay lại với nó sớm, bởi vì constructor là một phần không thể thiếu trong lập trình hướng đối tượng.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s