30/09/2018, 16:04

Downcast dùng dynamic_cast trả về NULL nhưng vẫn có thể gọi hàm?

Hi mọi người, vừa qua trong quá trình tìm hiểu downcast mình có một thắc mắc nhỏ như sau:
Giả sử mình có 2 class A và B kế thừa A :

class A{
public:
    virtual void Func() { cout << "in A" << endl; }
};
class B : public A
{    
public:
    virtual void Func() { cout << "in B" << endl; }
    void Jump() {cout << "B can fly" << endl;}
};

Trong hàm main() mình thực hiện như sau:

A* ob = new A();
if (dynamic_cast<B*>(ob) == NULL)
    cout << "Null" << endl;
dynamic_cast<B*>(ob)->Jump();

Như vậy mình đã thực hiện downcast con trỏ ob từ kiểu Base xuống Derive. Kết quả của việc cast này trả ra Null nhưng khi mình dùng nó để truy xuất hàm Jump thì vẫn truy xuất thành công??? Đây là điều mà mình thắc mắc tại sao lại có thể truy xuất được bằng con trỏ Null!?

Bên cạnh đó, khi mình dùng con trỏ này truy xuất 1 hàm virtual, cụ thể là hàm Func() thì bị dump ngay

Nguyễn Minh Dũng viết 18:10 ngày 30/09/2018

Đây là một câu hỏi hay mà khó Phải lật lại sách coi mới giải thích được.

tại sao lại có thể truy xuất được bằng con trỏ Null!

Đây đúng là con trỏ NULL, nhưng là NULL pointer có kiểu B (Derived). Khi nó gọi hàm Jump thì nó vẫn thực hiện được. Vì con trỏ kiểu B biết được nó có hàm Jump. Nhưng ở đây Khang còn may mắn một điều nữa đó là hàm Jump này là hàm non-virtual(không cần phải trỏ tới vtable) và không sử dụng bất cứ thứ gì thuộc về một class riêng biệt (tức là con trỏ this).

  • Trước hết nói về con trỏ this, Đạt sửa lại hàm Jump như sau, có sử dụng con trỏ this.
class B : public A
{
public:
    virtual void Func()
    {
        cout << "in B" << endl;
    }
    void Jump()
    {
        cout << "B can fly" << endl;
        x = 1; // gán biến x = 1, tức this->x = 1
    }
private:
    int x;
};

Thì khi này con trỏ NULL kiểu B sẽ chết ngay, vì nó không biết x ở đâu.

  • Về hàm virtual, để thực hiện lời gọi tới hàm virtual ta cần tới vtable.

Bên cạnh đó, khi mình dùng con trỏ này truy xuất 1 hàm virtual, cụ thể là hàm Func() thì bị dump ngay

Trong trường hợp này ta đã biết ob là con trỏ NULL kiểu B. Con trỏ NULL thì nó không có vtable, hay nói chính xác hơn là nó không trỏ tới vtable nào cả. Vì thế cho nên khi ta gọi tới hàm virtual thì sẽ bị crash.

viết 18:17 ngày 30/09/2018

He He thank a @ltd. Ngon lành rồi. Hồi đầu e cũng có thử tạo thêm 1 biến ở class B. Lúc đó xài còn trỏ Null là banh ngay Ok e hiểu được vấn đề rồi. Nhân tiện e có thể thắc mắc thêm 1 tí về cái dynamic_cast và vtable ko ạ?

Bonus Querry: E đọc trên mạng và tài liệu, đa phần đều nói dynamic_cast dựa vào thông tin của vtable để thực hiện nhưng không thấy nói rõ. A có thể hiện thực nó ra bằng hình ảnh 1 tí được không

Nguyễn Minh Dũng viết 18:13 ngày 30/09/2018

Anh thấy video này nói về vtable rất trực quan, anh hiểu được vtable là nhờ video này

ddhuy137 viết 18:18 ngày 30/09/2018

Mình thử làm 1 bài test nhỏ:

class A
{
    private:
        int i;
    public:
        void non_vfunc() {}
};

class B : public A
{
    public:
        virtual void vfunc() {}
};

Và đây là hàm main, ( mình compile trên x64)

int main(int argc, char** argv)
{
    cout << "sizeof(A): " << sizeof(A) << endl;
    cout << "sizeof(B): " << sizeof(B) << endl;
    return 0;
}

Kết quả: ta thấy rằng chỉ class nào có virtual function thì mới có vpointer. (Trong các tài liệu C++ đều có nói về cái này, mình làm test thử để chắc thêm )

sizeof(A): 4
sizeof(B): 16

Việc downcast từ A xuống B không thể tạo ra thêm variable được (cũng như vd của a Đạt thêm 1 biến int vào, vì mình new A, thì chỉ đc cấp phát memory bằng size của A thôi). Nên nếu gọi 1 virtual function thì sẽ bị dump ngay, vì virtual function phải được gọi bằng vpointer trỏ tới vtable. Mà lúc này ta làm gì có vpointer của class A

Còn việc so sánh với NULL, thử để function gọi hàm của B trong block của if xem thử nó có gọi đc ko. Có thể trong C++ mình nên so sánh với nullptr thì hay hơn là NULL, vì NULL chỉ là:

#define NULL 0

Và việc gọi được hàm của B thì chắc là do cách nó phân giải lời gọi hàm thông qua cái gọi là name mangling trong C++ lúc compiled time

Câu hỏi này hay quá

viết 18:17 ngày 30/09/2018

Thank man, sắp tới sẽ cố gắng làm rõ được cái vpointer cái pointer trỏ tới class nó liên hệ sao. Vì lúc downcast nó có sử dụng thông tin của Vtable để lựa chọn class derive. Mà cài này còn đang hiểu nhập nhằng quá chưa share dc

Nhưng về bản chất thì virtual giúp mình hiện thực từ class cha xuống để sử dụng nhiều class con. Cũng ít phổ biến việc sử dụng downcast từ class cha xuống. Chỉ là lúc làm cái này thấy nó kì nên bay lên hỏi

Bài liên quan
0