2016-09-02 13 views

Trả lời

73

Đó là vì std::shared_ptr thực hiện kiểu tẩy xoá, trong khi std::unique_ptr không.


Kể từ std::shared_ptr thực hiện kiểu tẩy xoá, nó cũng hỗ trợ khác bất động sản hấp dẫn, tức. số điện thoại không phải cần loại số deleter làm đối số loại mẫu cho mẫu lớp học. Nhìn vào tờ khai của họ:

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

trong đó có Deleter như tham 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 shared_ptr triển khai loại xóa? Vâng, nó làm như vậy, bởi vì nó có hỗ trợ tham chiếu, và để hỗ trợ điều này, nó phải cấp phát bộ nhớ từ heap và vì nó phải cấp phát bộ nhớ, nó đi thêm một bước nữa và thực hiện loại xóa — cũng cần phân bổ đống. Vì vậy, về cơ bản nó chỉ là cơ hội!

Bởi vì kiểu tẩy xoá, std::shared_ptr là khả năng hỗ trợ hai điều:

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

Được rồi. Đó là tất cả về cách std::shared_ptr hoạt động.

Bây giờ câu hỏi là, có thể std::unique_ptr lưu trữ các đối tượng void*? Vâng, câu trả lời là, — miễn là bạn vượt qua một deleter phù hợp làm đối số.Dưới đây là một trong những cuộc biểu tình 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 ;-) 

Output (online demo):

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 loại xóa deleter, nhưng có vẻ như có thể là tốt (với chi phí của một số phân bổ đống). Về cơ bản, điều này có nghĩa là có thực sự là một điểm thích hợp cho một loại 3 của con trỏ thông minh: một con trỏ thông minh độc quyền sở hữu với loại tẩy xoá.

@Steve Jessop đề nghị các giải pháp sau đây,

Tôi chưa bao giờ thực sự cố gắng này, nhưng có lẽ bạn có thể đạt được điều đó bằng cách sử dụng một cách phù hợp std::function như các loại deleter với unique_ptr? Giả sử rằng thực sự hoạt động sau đó bạn đã hoàn thành, độc quyền sở hữu và một deleter loại xóa.

Tiếp theo đề nghị này, tôi thực hiện điều này,

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

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

template<typename T> 
auto unique_void(T * ptr) -> unique_void_ptr 
{ 
    return unique_void_ptr(ptr, &deleter<T>); 
} 

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")); 
} 

Output (online demo):

{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.

+9

Câu trả lời hay, +1. Nhưng bạn có thể làm cho nó tốt hơn bằng cách đề cập rõ ràng rằng một 'std :: unique_ptr ' vẫn có thể bằng cách cung cấp một 'D' phù hợp. – Angew

+1

@Angrew: Đẹp nhất, 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;) –

+0

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

5

Một trong những lý do là một trong nhiều trường hợp sử dụng của một shared_ptr - cụ thể là chỉ báo suốt đời hoặc sentinel.

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_target là một cái gì đó 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 cái gì đó gọi lại như thế này:

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 
    } 
}; 

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

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

Các vấn đề liên quan