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 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:
Đó là bởi vì std::shared_ptr
thực hiện tẩy xóa kiểu, trong khi std::unique_ptr
không.
Kể từ khi std::shared_ptr
thự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;
có Deleter
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 lại shared_ptr
triể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_ptr
có thể hỗ trợ hai điều:
void*
, 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 .Ổn thỏa. Đó 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 như void*
? Chà, câu trả lời là, có - 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.
mà @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::function
làm loại trình duyệt vớiunique_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::function
vì 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.
std::function
là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.
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_target
là 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>::lock
là một hoạt động đồng bộ.
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ợpD
.