C++ Programming – Overlapping and delegating constructors


Constructors with overlapping functionality

Như các bạn đã biết, khi chúng ta tạo một đối tượng (object), thì constructor của object sẽ được gọi ngầm (nghĩa là nó gọi và nó thực thi, nhưng chúng ta thì không thấy) bởi compiler C++. Và trong một class, chúng ta có thể có nhiều constructor (chúng ta đã học từ trước), và chúng ta gọi điều này là overlapping functionality (Sau này chúng ta có thể gọi là function overloading).

class Foo
{
public:
    Foo()
    {
        // code to do A
    }
 
    Foo(int value)
    {
        // code to do A
        // code to do B
    }
};

Class trên đây có 2 constructor: một default constructor và một constructor có tham số. “code to do A” đểu có trong cả 2 constructor. Việc có 2 đoạn code trùng nhau trong một chương trình là không nên, và các lập trình viên phải tránh nó khi có thể. Chúng ta sẽ học cách làm sao để không có 2 đoạn code trùng lặp trong một chương trình.

The obvious solution doesn’t work prior to C++11

Chúng ta có giải pháp đầu tiên là gọi thẳng constructor Foo() ở trong phần thân của constructor Foo(int) để thực hiện đoạn code A.

class Foo
{
public:
    Foo()
    {
        // code to do A
    }
 
    Foo(int value)
    {
        Foo(); // use the above constructor to do A
        // code to do B
    }
};

Hay chúng ta có thể dùng cái cách mà chúng ta đã học:

class Foo
{
public:
    Foo()
    {
        // code to do A
    }
 
    Foo(int value): Foo() // use the above constructor to do A
    {
        // code to do B
    }
};

Tuy nhiên, trước C++ 11, nếu bạn muốn một constructor gọi một constructor khác, chương trình vẫn sẽ chạy, nhưng nó sẽ làm việc không như bạn đã kì vọng. Và bạn có thể sẽ phải dành nhiều thời gian cho việc tìm lỗi và debug, rất may bây giờ đa số chúng ta dùng C++ 11, và b.

Using a separate function

Trong constructor, chúng ta có thể thêm những biến membermethod của class vào trong đó, và đương nhiên những biến member và method này phải được định nghĩa (declare) trước đó (dĩ nhiên là bên trong class). Và bạn đương nhiên có thể copy code A để sử dụng lại, và chúng ta có sự lặp lại code, đều này gây khó hiểu cho người lập trình viên và việc bảo trì.

Phương pháp tốt nhất để giải quyết vấn đề này chúng ta sẽ viết một method riêng để thực hiện đoạn code A và cả hai constructor Foo()Foo(int) sẽ gọi method này để thực hiện đoạn code A và tránh việc trùng lặp.

class Foo
{
private:
    void DoA()
    {
        // code to do A
    }
 
public:
    Foo()
    {
        DoA();
    }
 
    Foo(int nValue)
    {
        DoA();
        // code to do B
    }
 
};

Tuy nhiên, trong trường hợp này, vẫn có sự trùng lặp code, chẳng qua chúng ta cho nó nhỏ lại bằng cái tên hàm.

Liên quan một chút tới việc sử dụng function như ví dụ trên, khi chúng ta muốn thiết lập lại giá trị mặc định của các biến member trong một object nào đó, không lẽ chúng ta đi gọi constructor để cho nó thiết lập lại các giá trị mặc định này, việc gọi trực tiếp constructor có thể gây ra những hành vi “nguy hiểm” của compiler. Chúng ta có thể sử dụng một function để thiết lập các giá trị mặc định này.

class Foo
{
public:
    Foo()
    {
        Init();
    }
 
    Foo(int value)
    {
        Init();
        // do something with value
    }
 
    void Init()
    {
        // code to init Foo
    }
};

Bây giờ bạn có thể gọi hàm Init() thỏa mái. Nhưng chú ý, nếu trong hàm có những pointer (cấp phát động) thì hãy cẩn thận và chú ý việc giải phóng bộ nhớ nhé.

Delegating constructors in C++11

Trong C++ 11 (cái này là chúng ta hay xài nhất), constructor được cho phép gọi constructor khác trong phần thân (body) của nó. Và việc này gọi là delegating constructor (hay constructor chaining).
Đây là ví dụ:

class Foo
{
private:
 
public:
    Foo()
    {
        // code to do A
    }
 
    Foo(int value): Foo() // use Foo() default constructor to do A
    {
        // code to do B
    }
 
};

Đoạn code trên này chạy cực kì chính xác. Và không biết các bạn có còn nhớ đây là kĩ thuật member initializer list?

Và đây là ví dụ tiếp theo sử dụng delegating constructor để xử lý đám code dài dòng:

#include <string>
#include <iostream>
 
class Employee
{
private:
    int m_id;
    std::string m_name;
 
public:
    Employee(int id=0, const std::string &name=""):
        m_id(id), m_name(name)
    {
        std::cout << "Employee " << m_name << " created.\n";
    }
 
    // Use a delegating constructors to minimize redundant code
    Employee(const std::string &name) : Employee(0, name) { }
};

Bài tiếp theo chúng ta sẽ qua destructor.

Advertisements

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