Câu trả lời:
Nó cho phép bạn có được một shared_ptr
ví dụ hợp lệ this
, khi tất cả những gì bạn có là this
. Nếu không có nó, bạn sẽ không có cách nào nhận được một shared_ptr
đến this
, trừ khi bạn đã có một là một thành viên. Ví dụ này từ tài liệu tăng cường cho enable_ Shared_from_this :
class Y: public enable_shared_from_this<Y>
{
public:
shared_ptr<Y> f()
{
return shared_from_this();
}
}
int main()
{
shared_ptr<Y> p(new Y);
shared_ptr<Y> q = p->f();
assert(p == q);
assert(!(p < q || q < p)); // p and q must share ownership
}
Phương thức f()
trả về một giá trị hợp lệ shared_ptr
, mặc dù nó không có cá thể thành viên. Lưu ý rằng bạn không thể đơn giản làm điều này:
class Y: public enable_shared_from_this<Y>
{
public:
shared_ptr<Y> f()
{
return shared_ptr<Y>(this);
}
}
Con trỏ được chia sẻ mà con số này trả về sẽ có số tham chiếu khác với con trỏ "thích hợp" và một trong số chúng sẽ bị mất và giữ tham chiếu lơ lửng khi đối tượng bị xóa.
enable_shared_from_this
đã trở thành một phần của tiêu chuẩn C ++ 11. Bạn cũng có thể lấy nó từ đó cũng như từ boost.
std::shared_ptr
std::enable_shared_from_this
std::shared_ptr
đối tượng đã được quản lý bởi người khác std::shared_ptr
sẽ không tham khảo tài liệu tham khảo yếu được lưu trữ nội bộ và do đó sẽ dẫn đến hành vi không xác định." ( en.cppreference.com/w/cpp/memory/enable_ Shared_from_this )
shared_ptr<Y> q = p
?
std::make_shared<T>
.
từ bài viết của Tiến sĩ Dobbs về con trỏ yếu, tôi nghĩ ví dụ này dễ hiểu hơn (nguồn: http://drdobbs.com/cpp/184402026 ):
... mã như thế này sẽ không hoạt động chính xác:
int *ip = new int;
shared_ptr<int> sp1(ip);
shared_ptr<int> sp2(ip);
Cả hai shared_ptr
đối tượng đều không biết về đối tượng kia, vì vậy cả hai sẽ cố gắng giải phóng tài nguyên khi chúng bị phá hủy. Điều đó thường dẫn đến các vấn đề.
Tương tự, nếu một hàm thành viên cần một shared_ptr
đối tượng sở hữu đối tượng mà nó được gọi, nó không thể tạo một đối tượng một cách nhanh chóng:
struct S
{
shared_ptr<S> dangerous()
{
return shared_ptr<S>(this); // don't do this!
}
};
int main()
{
shared_ptr<S> sp1(new S);
shared_ptr<S> sp2 = sp1->dangerous();
return 0;
}
Mã này có cùng một vấn đề như ví dụ trước, mặc dù ở dạng tinh tế hơn. Khi nó được xây dựng, shared_pt
đối tượng r sp1
sở hữu tài nguyên mới được phân bổ. Mã bên trong hàm thành viên S::dangerous
không biết về shared_ptr
đối tượng đó , vì vậy shared_ptr
đối tượng mà nó trả về khác với sp1
. Sao chép shared_ptr
đối tượng mới để sp2
không giúp đỡ; khi sp2
đi ra khỏi phạm vi, nó sẽ giải phóng tài nguyên và khi sp1
đi ra khỏi phạm vi, nó sẽ giải phóng tài nguyên một lần nữa.
Cách để tránh vấn đề này là sử dụng mẫu lớp enable_shared_from_this
. Mẫu lấy một đối số kiểu mẫu, là tên của lớp xác định tài nguyên được quản lý. Đến lượt, lớp đó phải được xuất phát công khai từ mẫu; như thế này:
struct S : enable_shared_from_this<S>
{
shared_ptr<S> not_dangerous()
{
return shared_from_this();
}
};
int main()
{
shared_ptr<S> sp1(new S);
shared_ptr<S> sp2 = sp1->not_dangerous();
return 0;
}
Khi bạn làm điều này, hãy nhớ rằng đối tượng mà bạn gọi shared_from_this
phải được sở hữu bởi một shared_ptr
đối tượng. Điều này sẽ không hoạt động:
int main()
{
S *p = new S;
shared_ptr<S> sp2 = p->not_dangerous(); // don't do this
}
shared_ptr<S> sp1(new S);
có thể được ưu tiên sử dụng shared_ptr<S> sp1 = make_shared<S>();
, hãy xem ví dụ stackoverflow.com/questions/18301511/
shared_ptr<S> sp2 = p->not_dangerous();
bởi vì điều đáng tiếc ở đây là bạn phải tạo một shared_ptr theo cách thông thường trước khi bạn gọi shared_from_this()
lần đầu tiên! Điều này thực sự dễ dàng để có được sai! Trước C ++ 17, UB phải gọi shared_from_this()
trước khi chính xác một shared_ptr đã được tạo theo cách thông thường: auto sptr = std::make_shared<S>();
hoặc shared_ptr<S> sptr(new S());
. Rất may từ C ++ 17 trở đi làm như vậy sẽ ném.
S* s = new S(); shared_ptr<S> ptr = s->not_dangerous();
<- Chỉ được phép gọi shared_from_this trên một đối tượng được chia sẻ trước đó, tức là trên một đối tượng được quản lý bởi std :: shared_ptr <T>. Mặt khác, hành vi không được xác định (cho đến khi C ++ 17) std :: bad_weak_ptr bị ném (bởi hàm tạo shared_ptr từ một yếu tố được xây dựng mặc định) (kể từ C ++ 17). . Vì vậy, thực tế là nó nên được gọi always_dangerous()
, bởi vì bạn cần kiến thức về việc nó đã được chia sẻ hay chưa.
Đây là lời giải thích của tôi, từ góc độ các loại hạt và bu lông (câu trả lời hàng đầu không 'nhấp chuột' với tôi). * Lưu ý rằng đây là kết quả của việc điều tra nguồn cho shared_ptr và enable_ Shared_from_this đi kèm với Visual Studio 2012. Có lẽ các trình biên dịch khác triển khai enable_spl_from_this khác nhau ... *
enable_shared_from_this<T>
thêm một weak_ptr<T>
ví dụ riêng T
chứa ' một số tham chiếu thực ' cho ví dụ của T
.
Vì vậy, khi bạn lần đầu tiên tạo một shared_ptr<T>
T * mới, điểm yếu bên trong của T * sẽ được khởi tạo với số lần truy cập là 1. Cơ shared_ptr
bản mới dựa vào điều nàyweak_ptr
.
T
sau đó, trong các phương thức của nó, có thể gọi shared_from_this
để lấy một thể hiện của các sao lưushared_ptr<T>
đó vào cùng một số tham chiếu được lưu trữ bên trong . Theo cách này, bạn luôn có một nơi T*
lưu trữ số lần giới thiệu thay vì có nhiều shared_ptr
trường hợp không biết về nhau và mỗi người nghĩ rằng họ là người shared_ptr
chịu trách nhiệm đếm T
và xóa nó khi họ tham gia -count đạt đến không.
So, when you first create...
bởi vì đó là một yêu cầu (như bạn nói, yếu_ptr không được khởi tạo cho đến khi bạn chuyển con trỏ đối tượng vào một ctor shared_ptr!) Và yêu cầu này là nơi mọi thứ có thể trở nên sai lầm khủng khiếp nếu bạn là không cẩn thận. Nếu bạn không tạo shared_ptr trước khi gọi, shared_from_this
bạn sẽ nhận được UB - tương tự như vậy nếu bạn tạo nhiều hơn một shared_ptr, bạn cũng nhận được UB. Bạn phải bằng cách nào đó đảm bảo rằng bạn tạo một shared_ptr chính xác một lần.
enable_shared_from_this
bắt đầu dễ vỡ vì vấn đề là có thể lấy shared_ptr<T>
từ a T*
, nhưng trong thực tế khi bạn nhận được một con trỏ T* t
, thường không an toàn khi thừa nhận bất cứ điều gì về nó đã được chia sẻ hay chưa, và đoán sai là UB.
Lưu ý rằng việc sử dụng boost :: intrusive_ptr không gặp phải vấn đề này. Đây thường là một cách thuận tiện hơn để khắc phục vấn đề này.
enable_shared_from_this
cho phép bạn làm việc với một API cụ thể chấp nhận shared_ptr<>
. Theo tôi, một API như vậy thường là Làm sai (vì tốt hơn là để thứ gì đó cao hơn trong ngăn xếp sở hữu bộ nhớ) nhưng nếu bạn buộc phải làm việc với API như vậy, thì đây là một lựa chọn tốt.
Nó giống hệt nhau trong c ++ 11 trở lên: Đó là cho phép khả năng quay trở lại this
như một con trỏ dùng chung vì this
cung cấp cho bạn một con trỏ thô.
nói cách khác, nó cho phép bạn biến mã như thế này
class Node {
public:
Node* getParent const() {
if (m_parent) {
return m_parent;
} else {
return this;
}
}
private:
Node * m_parent = nullptr;
};
vào đây:
class Node : std::enable_shared_from_this<Node> {
public:
std::shared_ptr<Node> getParent const() {
std::shared_ptr<Node> parent = m_parent.lock();
if (parent) {
return parent;
} else {
return shared_from_this();
}
}
private:
std::weak_ptr<Node> m_parent;
};
shared_ptr
. Bạn có thể muốn thay đổi giao diện để đảm bảo đó là trường hợp.
std::shared_ptr<Node> getParent const()
, tôi thường sẽ phơi bày nó NodePtr getParent const()
thay vào đó. Nếu bạn thực sự cần quyền truy cập vào con trỏ thô bên trong (ví dụ tốt nhất: giao dịch với thư viện C), thì std::shared_ptr<T>::get
tôi sẽ không đề cập đến điều này vì tôi đã sử dụng trình truy cập con trỏ thô này vì sử dụng quá nhiều lần vì lý do sai.
Một cách khác là thêm một weak_ptr<Y> m_stub
thành viên vào class Y
. Sau đó viết:
shared_ptr<Y> Y::f()
{
return m_stub.lock();
}
Hữu ích khi bạn không thể thay đổi lớp bạn đang bắt nguồn (ví dụ: mở rộng thư viện của người khác). Đừng quên khởi tạo thành viên, ví dụ: m_stub = shared_ptr<Y>(this)
nó có hiệu lực ngay cả trong khi xây dựng.
Sẽ ổn nếu có nhiều sơ khai như thế này trong hệ thống phân cấp thừa kế, nó sẽ không ngăn chặn sự phá hủy của đối tượng.
Chỉnh sửa: Như được chỉ định chính xác bởi nobar người dùng, mã sẽ phá hủy đối tượng Y khi quá trình gán kết thúc và các biến tạm thời bị hủy. Do đó, câu trả lời của tôi là không chính xác.
shared_ptr<>
cái mà không xóa điểm của nó, thì điều này là quá mức cần thiết. Bạn có thể chỉ cần nói return shared_ptr<Y>(this, no_op_deleter);
nơi no_op_deleter
một đối tượng hàm unary lấy Y*
và không làm gì.
m_stub = shared_ptr<Y>(this)
sẽ xây dựng và hủy ngay lập tức một shared_ptr tạm thời từ đây. Khi tuyên bố này kết thúc, this
sẽ bị xóa và tất cả các tài liệu tham khảo tiếp theo sẽ được treo lủng lẳng.
enable_shared_from_this
, nó sẽ giữ một weak_ptr
chính nó (được điền bởi ctor), được trả về như một shared_ptr
khi bạn gọi shared_from_this
. Nói cách khác, bạn đang nhân đôi những gì enable_shared_from_this
đã cung cấp.