Tại sao shared_ptr <void> là hợp pháp, trong khi unique_ptr <void> là không hợp pháp?


100

Câu hỏi thực sự phù hợp với tiêu đề: Tôi tò mò muốn biết lý do kỹ thuật cho sự khác biệt này là gì, nhưng cũng là cơ sở lý do?

std::shared_ptr<void> sharedToVoid; // legal;
std::unique_ptr<void> uniqueToVoid; // ill-formed;

Câu trả lời:


118

Đó là bởi vì std::shared_ptrthực hiện tẩy xóa kiểu, trong khi std::unique_ptrkhông.


Kể từ khi std::shared_ptrthực hiện xóa kiểu, nó cũng hỗ trợ một thuộc tính thú vị khác , viz. nó không cần kiểu của đối số deleter làm kiểu mẫu cho mẫu lớp. Nhìn vào tuyên bố của họ:

template<class T,class Deleter = std::default_delete<T> > 
class unique_ptr;

Deletertham số kiểu, trong khi

template<class T> 
class shared_ptr;

không có nó.

Bây giờ câu hỏi đặt ra là, tại sao lại shared_ptrtriển khai tính năng xóa kiểu? Chà, nó làm được như vậy, bởi vì nó phải hỗ trợ đếm tham chiếu và để hỗ trợ điều này, nó phải cấp phát bộ nhớ từ heap và vì dù sao thì nó cũng phải cấp phát bộ nhớ, nó tiến thêm một bước nữa và thực hiện xóa kiểu - cần heap phân bổ quá. Vì vậy, về cơ bản nó chỉ là cơ hội!

Vì tính năng tẩy xóa kiểu chữ, std::shared_ptrcó thể hỗ trợ hai điều:

  • Nó có thể lưu trữ các đối tượng thuộc bất kỳ loại nàovoid* , nhưng nó vẫn có thể xóa các đối tượng đang bị phá hủy một cách chính xác bằng cách gọi chính xác trình hủy của chúng .
  • Kiểu của trình xóa không được chuyển làm đối số kiểu cho mẫu lớp, có nghĩa là một chút tự do mà không ảnh hưởng đến an toàn kiểu .

Ổn thỏa. Đó là tất cả về cách std::shared_ptrhoạt động.

Bây giờ câu hỏi là, có thể std::unique_ptrlưu trữ các đối tượng như void* ? Chà, câu trả lời là, - miễn là bạn chuyển một trình duyệt phù hợp làm đối số. Đây là một trong những minh chứng như vậy:

int main()
{
    auto deleter = [](void const * data ) {
        int const * p = static_cast<int const*>(data);
        std::cout << *p << " located at " << p <<  " is being deleted";
        delete p;
    };

    std::unique_ptr<void, decltype(deleter)> p(new int(959), deleter);

} //p will be deleted here, both p ;-)

Đầu ra ( bản demo trực tuyến ):

959 located at 0x18aec20 is being deleted

Bạn đã hỏi một câu hỏi rất thú vị trong bình luận:

Trong trường hợp của tôi, tôi sẽ cần một trình xóa xóa kiểu, nhưng có vẻ như nó cũng có thể (với chi phí của một số phân bổ heap). Về cơ bản, điều này có nghĩa là thực sự có một vị trí thích hợp cho loại con trỏ thông minh thứ 3: con trỏ thông minh sở hữu độc quyền với tính năng xóa kiểu.

@Steve Jessop đã đề xuất giải pháp sau,

Tôi chưa bao giờ thực sự thử điều này, nhưng có lẽ bạn có thể đạt được điều đó bằng cách sử dụng thích hợp std::functionlàm loại trình duyệt với unique_ptr? Giả sử nó thực sự hoạt động thì bạn đã hoàn thành, quyền sở hữu độc quyền và trình xóa loại đã xóa.

Theo gợi ý này, tôi đã thực hiện điều này (mặc dù nó không được sử dụng std::functionvì nó có vẻ không cần thiết):

using unique_void_ptr = std::unique_ptr<void, void(*)(void const*)>;

template<typename T>
auto unique_void(T * ptr) -> unique_void_ptr
{
    return unique_void_ptr(ptr, [](void const * data) {
         T const * p = static_cast<T const*>(data);
         std::cout << "{" << *p << "} located at [" << p <<  "] is being deleted.\n";
         delete p;
    });
}

int main()
{
    auto p1 = unique_void(new int(959));
    auto p2 = unique_void(new double(595.5));
    auto p3 = unique_void(new std::string("Hello World"));
}  

Đầu ra ( bản demo trực tuyến ):

{Hello World} located at [0x2364c60] is being deleted.
{595.5} located at [0x2364c40] is being deleted.
{959} located at [0x2364c20] is being deleted.

Hy vọng rằng sẽ giúp.


13
Câu trả lời hay, +1. Nhưng bạn có thể làm cho nó tốt hơn nữa bằng cách đề cập rõ ràng rằng a std::unique_ptr<void, D>vẫn có thể thực hiện được bằng cách cung cấp một cái phù hợp D.
Angew không còn tự hào về SO

1
@Angrew: Tuyệt vời, bạn đã tìm thấy câu hỏi cơ bản thực sự không được viết trong câu hỏi của tôi;)
Ad N

@Nawaz: Cảm ơn bạn. Trong trường hợp của tôi, tôi sẽ cần một trình xóa xóa kiểu, nhưng có vẻ như nó cũng có thể (với chi phí của một số phân bổ heap). Về cơ bản, điều này có nghĩa là thực sự có một vị trí thích hợp cho loại con trỏ thông minh thứ 3: con trỏ thông minh sở hữu độc quyền với tính năng xóa loại?
Quảng cáo N

8
@AdN: Tôi chưa bao giờ thực sự thử điều này, nhưng có lẽ bạn có thể đạt được điều đó bằng cách sử dụng thích hợp std::functionlàm loại trình xóa với unique_ptr? Giả sử nó thực sự hoạt động thì bạn đã hoàn thành, quyền sở hữu độc quyền và trình xóa loại đã xóa.
Steve Jessop

Ngữ pháp nit: "tại sao X động từ Y?" cần được "tại sao không X động từ Y?" bằng tiếng Anh.
zwol

7

Một trong những lý do nằm trong một trong nhiều trường hợp sử dụng của a shared_ptr- cụ thể là như một chỉ báo thời gian tồn tại hoặc lính canh.

Điều này đã được đề cập trong tài liệu thúc đẩy ban đầu:

auto register_callback(std::function<void()> closure, std::shared_ptr<void> pv)
{
    auto closure_target = { closure, std::weak_ptr<void>(pv) };
    ...
    // store the target somewhere, and later....
}

void call_closure(closure_target target)
{
    // test whether target of the closure still exists
    auto lock = target.sentinel.lock();
    if (lock) {
        // if so, call the closure
        target.closure();
    }
}

Đâu closure_targetlà thứ như thế này:

struct closure_target {
    std::function<void()> closure;
    std::weak_ptr<void> sentinel;
};

Người gọi sẽ đăng ký một cuộc gọi lại như sau:

struct active_object : std::enable_shared_from_this<active_object>
{
    void start() {
      event_emitter_.register_callback([this] { this->on_callback(); }, 
                                       shared_from_this());
    }

    void on_callback()
    {
        // this is only ever called if we still exist 
    }
};

bởi vì shared_ptr<X>luôn có thể chuyển đổi thành shared_ptr<void>, event_emitter bây giờ có thể không biết về loại đối tượng mà nó đang gọi lại.

Sự sắp xếp này giải phóng người đăng ký với người phát sự kiện có nghĩa vụ xử lý các trường hợp giao nhau (điều gì sẽ xảy ra nếu cuộc gọi lại trong hàng đợi, chờ được thực hiện trong khi active_object biến mất?), Và cũng có nghĩa là không cần đồng bộ hóa việc hủy đăng ký. weak_ptr<void>::locklà một hoạt động đồng bộ.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.