0

shared_ptr

Tham khảo:

Vấn đề: Trước khi có con trỏ thông minh trong c++ 11, khi tạo ra một đối tượng class (ví dụ: class Home). Nếu không sử dụng nữa ta cần phải sử dụng delete để phá hủy class Home đó, tránh nó tồn tại trong bộ nhớ gây ra memory leak.

std::shared_ptr được tạo ra để tự động hóa vấn đề này bằng cơ chế "Đếm người dùng" (Reference Counting)

  • Chia sẽ sở hữu: Một ngôi nhà có thể có nhiều chủ sở hữu. Trong code, nhiều std::shared_ptr khác nhau có thể cùng trỏ tới một đối tượng trong bộ nhớ.
  • Tự động dọn dẹp đối tượng: Nếu số người sở hữu (hay shared_ptr) về 0

Vòng đời của dynamic object: Là khoảng thời gian mà nó được tạo ra trong bộ nhớ lúc chương trình chạy, cho đến khi nó bị phá hủy trong bộ nhớ.

Ví dụ:

#include <iostream>
#include <memory> 

class House {
public:
    House() { std::cout << "  ==> [Khai báo] Mot ngoi nha vua duoc xay dung!\n"; }
    ~House() { std::cout << "  ==> [Huy bo] Ngoi nha da bi pha do de giai phong bo nho!\n"; }
    void RingBell() { std::cout << "  Bong... Binh... (Tieng chuong cua ngoi nha)\n"; }
};

int main() {
    std::cout << "=== BUOC 1: Nguoi dau tien (ptr1) mua nha ===" << std::endl;
    
    // 1. Cấp phát động một ngôi nhà bằng toán tử 'new House()' -> trả về con trỏ thô (raw pointer)
    // 2. Đưa con trỏ thô đó vào hàm khởi tạo của std::shared_ptr để nó bắt đầu quản lý
    std::shared_ptr<House> ptr1(new House());
    
    std::cout << "Dia chi vung nho cua ngoi nha: " << ptr1.get() << std::endl;
    std::cout << "So nguoi dang dong so huu: " << ptr1.use_count() << std::endl;

    std::cout << "\n=== BUOC 2: Nguoi thu hai (ptr2) cung tham gia so huu ===" << std::endl;
    // ptr2 copy từ ptr1, tăng bộ đếm lên 2
    std::shared_ptr<House> ptr2 = ptr1;

    std::cout << "Dia chi ptr1 dang tro toi: " << ptr1.get() << std::endl;
    std::cout << "Dia chi ptr2 dang tro toi: " << ptr2.get() << std::endl;
    std::cout << "So nguoi dong so huu hien tai: " << ptr1.use_count() << std::endl;

    std::cout << "\n=== BUOC 3: Dung ptr2 de dieu khien ngoi nha ===" << std::endl;
    ptr2->RingBell(); 

    std::cout << "\n=== BUOC 4: Nguoi dau tien (ptr1) rut von ===" << std::endl;
    ptr1.reset(); 
    
    std::cout << "Sau khi ptr1 rut von, ptr1 dang tro toi: " << ptr1.get() << " (NULL)" << std::endl;
    std::cout << "Ngoi nha van con do tai dia chi: " << ptr2.get() << std::endl;
    std::cout << "So nguoi con dong so huu: " << ptr2.use_count() << std::endl;

    std::cout << "\n=== BUOC 5: Ket thuc chuong trinh ===" << std::endl;
    return 0;
}

}

Để tối ưu bộ nhớ và hiệu năng ta dùng make_shared. Muốn hiểu rõ hơn vì sao ta cần phải xem xét:

Một std::shared_ptr quản lí đối tượng cần 2 thứ:

  • Một con trỏ trỏ đến chính đối tượng đó
  • Khối quản lí nơi chứa bộ đếm use_count

Khi dùng toán tử new thuần túy

std::shared_ptr<House> ptr(new House());

Hệ thống phải 2 lần xin cấp phát bộ nhớ riêng biệt nằm rải rác ở 2 nơi khác nhau trên Heap

  • Lần 1: Cấp phát cho new House()
  • Lần 2: Cấp phát cho khối quản lí Control Block

Hệ quả: Tốn thời gian vì phải xin bộ nhớ 2 lần. Đồng thời, 2 vùng nhớ này nằm rải rác trên RAM, khiến CPU truy cập kém hiệu quả hơn

Nếu dùng make_shared

auto ptr = std::make_shared<House>();

Hệ thống gộp cả hai làm một! Nó tính toán tổng kích thước của House + Control Block rồi chỉ xin cấp phát bộ nhớ đúng 1 lần duy nhất. Cả hai nằm sát cạnh nhau trong RAM => Chương trình chạy nhanh hơn đáng kể và tiết kiệm dung lượng quản lý của hệ điều hành.

Nếu House có các tham số khởi tạo (ví dụ House(std::string address, std::string color)), ta truyền trực tiếp vào trong ngoặc

auto ptr = std::make_shared<House>("Hồ Chí Minh", "red")

All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí