ฉันวิ่งข้ามenable_shared_from_this
ในขณะที่อ่านตัวอย่าง Boost.Asio และหลังจากอ่านเอกสารฉันยังคงหลงทางว่าควรใช้สิ่งนี้อย่างถูกต้องอย่างไร ใครช่วยได้โปรดยกตัวอย่างและคำอธิบายว่าเมื่อใดที่ใช้คลาสนี้
ฉันวิ่งข้ามenable_shared_from_this
ในขณะที่อ่านตัวอย่าง Boost.Asio และหลังจากอ่านเอกสารฉันยังคงหลงทางว่าควรใช้สิ่งนี้อย่างถูกต้องอย่างไร ใครช่วยได้โปรดยกตัวอย่างและคำอธิบายว่าเมื่อใดที่ใช้คลาสนี้
คำตอบ:
จะช่วยให้คุณได้รับที่ถูกต้องshared_ptr
เช่นการเมื่อทั้งหมดที่คุณมีthis
this
หากไม่มีมันคุณจะไม่มีทางshared_ptr
ไปถึงthis
ได้เว้นแต่ว่าคุณมีสมาชิกอยู่แล้ว ตัวอย่างนี้จากเอกสารบูสต์สำหรับ 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
}
วิธีการf()
คืนค่าที่ถูกต้องshared_ptr
แม้ว่ามันจะไม่มีอินสแตนซ์ของสมาชิก โปรดทราบว่าคุณไม่สามารถทำได้:
class Y: public enable_shared_from_this<Y>
{
public:
shared_ptr<Y> f()
{
return shared_ptr<Y>(this);
}
}
ตัวชี้ที่ใช้ร่วมกันว่าสิ่งที่ส่งคืนนี้จะมีจำนวนการอ้างอิงที่แตกต่างจากที่ "เหมาะสม" และหนึ่งในนั้นจะสิ้นสุดการสูญเสียและถือการอ้างอิงห้อยเมื่อวัตถุถูกลบ
enable_shared_from_this
ได้กลายเป็นส่วนหนึ่งของมาตรฐาน C ++ 11 คุณยังสามารถรับมันได้จากที่นั่นหรือจากการเพิ่ม
std::shared_ptr
คอนสตรัคบนตัวชี้ดิบ ถ้าstd::enable_shared_from_this
มันสืบทอดมาจาก ฉันไม่รู้ว่าการปรับปรุงความหมายของ Boost ได้รับการอัปเดตเพื่อรองรับหรือไม่
std::shared_ptr
สำหรับวัตถุที่จัดการโดยผู้อื่นแล้วstd::shared_ptr
จะไม่ปรึกษาการอ้างอิงที่อ่อนแอที่เก็บไว้ภายในและดังนั้นจะนำไปสู่พฤติกรรมที่ไม่ได้กำหนด" ( en.cppreference.com/w/cpp/memory/enable_shared_from_this )
shared_ptr<Y> q = p
ไม่ได้เหรอ?
std::make_shared<T>
ควรจะใช้
จากบทความดร. โดบส์เกี่ยวกับตัวชี้ที่อ่อนแอฉันคิดว่าตัวอย่างนี้ง่ายต่อการเข้าใจ (แหล่งที่มา: http://drdobbs.com/cpp/184402026 ):
... รหัสเช่นนี้ทำงานไม่ถูกต้อง:
int *ip = new int;
shared_ptr<int> sp1(ip);
shared_ptr<int> sp2(ip);
shared_ptr
วัตถุทั้งสองไม่มีความรู้เกี่ยวกับวัตถุอื่นดังนั้นทั้งสองจะพยายามปล่อยทรัพยากรเมื่อวัตถุถูกทำลาย ซึ่งมักจะนำไปสู่ปัญหา
ในทำนองเดียวกันหากฟังก์ชันสมาชิกต้องการshared_ptr
วัตถุที่เป็นเจ้าของวัตถุที่ถูกเรียกมันจะไม่สามารถสร้างวัตถุได้ทันที
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;
}
รหัสนี้มีปัญหาเช่นเดียวกับตัวอย่างก่อนหน้านี้แม้ว่าจะอยู่ในรูปแบบที่ลึกซึ้งยิ่งขึ้น เมื่อมันถูกสร้างขึ้นshared_pt
วัตถุ r sp1
เป็นเจ้าของทรัพยากรที่จัดสรรใหม่ รหัสภายในฟังก์ชันสมาชิกที่S::dangerous
ไม่ทราบเกี่ยวกับว่าshared_ptr
วัตถุดังนั้นวัตถุที่มันกลับแตกต่างจากshared_ptr
sp1
การคัดลอกshared_ptr
วัตถุใหม่ไปsp2
ไม่ช่วย เมื่อsp2
ออกนอกขอบเขตจะปล่อยทรัพยากรและเมื่อsp1
ออกนอกขอบเขตจะปล่อยทรัพยากรอีกครั้ง
enable_shared_from_this
วิธีที่จะหลีกเลี่ยงปัญหานี้คือการใช้แม่แบบชั้นเรียน เท็มเพลตรับอาร์กิวเมนต์ชนิดเทมเพลตหนึ่งรายการซึ่งเป็นชื่อของคลาสที่กำหนดรีซอร์สที่ถูกจัดการ ในชั้นเรียนนั้นจะต้องได้รับการเปิดเผยต่อสาธารณะจากเทมเพลต แบบนี้:
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;
}
เมื่อคุณทำเช่นนี้โปรดทราบว่าวัตถุที่คุณโทรshared_from_this
ต้องเป็นเจ้าของโดยshared_ptr
วัตถุ สิ่งนี้จะไม่ทำงาน:
int main()
{
S *p = new S;
shared_ptr<S> sp2 = p->not_dangerous(); // don't do this
}
shared_ptr<S> sp1(new S);
นี้อาจต้องการใช้งานshared_ptr<S> sp1 = make_shared<S>();
แทนดูstackoverflow.com/questions/18301511/…
shared_ptr<S> sp2 = p->not_dangerous();
เพราะข้อผิดพลาดที่นี่คือคุณต้องสร้าง shared_ptr แบบปกติก่อนที่คุณจะโทรshared_from_this()
ครั้งแรก! นี่มันผิดง่ายจริงๆ! ก่อน C ++ 17 มันเป็นUBจะเรียกshared_from_this()
ก่อนที่จะตรงหนึ่ง shared_ptr ได้ถูกสร้างขึ้นด้วยวิธีปกติ: หรือauto sptr = std::make_shared<S>();
shared_ptr<S> sptr(new S());
โชคดีจาก C ++ 17 เป็นต้นไปที่ทำเช่นนี้จะโยน
S* s = new S(); shared_ptr<S> ptr = s->not_dangerous();
<- ได้รับอนุญาตให้เรียก shared_from_this บนวัตถุที่ใช้ร่วมกันก่อนหน้านี้เท่านั้นคือบนวัตถุที่จัดการโดย std :: shared_ptr <T> มิฉะนั้นการทำงานจะไม่ได้กำหนด (จนถึง C ++ 17) std :: bad_weak_ptr ถูกโยน (โดยตัวสร้าง shared_ptr จากค่าเริ่มต้นที่สร้างขึ้นอ่อนแอ _this) (ตั้งแต่ C ++ 17) . ดังนั้นความจริงก็คือมันควรจะเรียกว่าalways_dangerous()
เพราะคุณต้องการความรู้ว่ามันได้รับการแบ่งปันแล้วหรือไม่
นี่คือคำอธิบายของฉันจากมุมมองของถั่วและ bolts (คำตอบด้านบนไม่ได้ 'คลิก' กับฉัน) * โปรดทราบว่านี่คือผลลัพธ์ของการตรวจสอบแหล่งที่มาสำหรับ shared_ptr และ enable_shared_from_this ที่มาพร้อมกับ Visual Studio 2012 บางทีคอมไพเลอร์อื่นอาจใช้ enable_shared_from_this นี้แตกต่างกัน ... *
enable_shared_from_this<T>
เพิ่มส่วนตัวweak_ptr<T>
เช่นไปT
ซึ่งถือ ' นับหนึ่งอ้างอิงจริง ' T
เช่นของ
ดังนั้นเมื่อคุณแรกสร้างshared_ptr<T>
บน T * ใหม่ที่ T * 's weak_ptr ภายในได้รับการเริ่มต้นได้ด้วย refcount 1. ให้ใหม่พื้นหลังบนนี้shared_ptr
weak_ptr
T
สามารถแล้วในวิธีการโทรshared_from_this
ที่จะได้รับตัวอย่างของshared_ptr<T>
ที่หลังบนเดียวกันจำนวนการอ้างอิงเก็บไว้ภายใน ด้วยวิธีนี้คุณจะมีที่เดียวที่T*
เก็บการนับจำนวนแทนที่จะเก็บหลาย ๆshared_ptr
กรณีที่ไม่ทราบซึ่งกันและกันและแต่ละคนคิดว่าพวกเขาเป็นที่shared_ptr
ที่รับผิดชอบการนับT
และลบเมื่อผู้อ้างอิง - จำนวนถึงศูนย์
So, when you first create...
เพราะว่ามันเป็นข้อกำหนด (อย่างที่คุณบอกว่า weak_ptr ไม่ได้ถูกเตรียมใช้งานจนกว่าคุณจะส่งตัวชี้วัตถุไปยัง ctor shared_ptr!) และข้อกำหนดนี้เป็นสิ่งที่ผิดพลาดอย่างน่ากลัวถ้าคุณเป็น ไม่ระวัง หากคุณไม่ได้สร้าง shared_ptr ก่อนการโทรshared_from_this
คุณจะได้รับ UB เช่นเดียวกันหากคุณสร้าง shared_ptr มากกว่าหนึ่งรายการคุณจะได้รับ UB ด้วย คุณต้องทำให้แน่ใจว่าคุณสร้าง shared_ptr เพียงครั้งเดียว
enable_shared_from_this
เปราะเริ่มต้นตั้งแต่จุดที่จะสามารถได้รับshared_ptr<T>
จาก a T*
แต่ในความเป็นจริงเมื่อคุณได้รับตัวชี้T* t
โดยทั่วไปจะไม่ปลอดภัยที่จะคิดอะไรเกี่ยวกับการแบ่งปันหรือไม่และ การเดาผิดคือ UB
โปรดทราบว่าการใช้ boost :: intrusive_ptr ไม่ประสบปัญหานี้ นี่เป็นวิธีที่สะดวกกว่าในการแก้ไขปัญหานี้
enable_shared_from_this
ช่วยให้คุณสามารถทำงานร่วมกับ API shared_ptr<>
ซึ่งเฉพาะยอมรับ ในความคิดของฉันเช่น API มักจะทำผิด (มันจะดีกว่าที่จะปล่อยให้สิ่งที่สูงกว่าในกองเป็นเจ้าของหน่วยความจำ) แต่ถ้าคุณถูกบังคับให้ทำงานกับ API เช่นนี้เป็นตัวเลือกที่ดี
มันเหมือนกันใน c ++ 11 และใหม่กว่า: เพื่อเปิดใช้งานความสามารถในการส่งคืนthis
เป็นตัวชี้ที่ใช้ร่วมกันเนื่องจากthis
ให้ตัวชี้แบบดิบแก่คุณ
กล่าวอีกนัยหนึ่งจะช่วยให้คุณเปิดรหัสเช่นนี้
class Node {
public:
Node* getParent const() {
if (m_parent) {
return m_parent;
} else {
return this;
}
}
private:
Node * m_parent = nullptr;
};
เป็นนี้
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
นี้จะทำงานถ้าวัตถุเหล่านี้มีการจัดการเสมอโดย คุณอาจต้องการเปลี่ยนอินเทอร์เฟซเพื่อให้แน่ใจว่าเป็นกรณีนี้
std::shared_ptr<Node> getParent const()
ฉันจะเปิดเผยตามปกติNodePtr getParent const()
แทน หากคุณต้องการเข้าถึงตัวชี้ raw ภายใน (ตัวอย่างที่ดีที่สุด: การจัดการกับไลบรารี C) มีอยู่std::shared_ptr<T>::get
สำหรับสิ่งที่ฉันเกลียดพูดถึงเพราะฉันได้ accessor ตัวชี้ดิบนี้ใช้หลายครั้งด้วยเหตุผลที่ผิด
อีกวิธีหนึ่งคือการเพิ่มสมาชิกเข้าไปในweak_ptr<Y> m_stub
class Y
จากนั้นเขียน:
shared_ptr<Y> Y::f()
{
return m_stub.lock();
}
มีประโยชน์เมื่อคุณไม่สามารถเปลี่ยนชั้นเรียนที่คุณได้รับ (เช่นขยายห้องสมุดของผู้อื่น) อย่าลืมที่จะเริ่มต้นสมาชิกเช่นโดยm_stub = shared_ptr<Y>(this)
มันถูกต้องแม้ในช่วงตัวสร้าง
มันก็โอเคถ้ามีสตับมากเช่นนี้ในลำดับชั้นการสืบทอดมันจะไม่ป้องกันการทำลายของวัตถุ
แก้ไข:ตามที่ผู้ใช้โนเบิลชี้ให้เห็นอย่างถูกต้องรหัสจะทำลายวัตถุ Y เมื่อการมอบหมายเสร็จสิ้นและตัวแปรชั่วคราวจะถูกทำลาย ดังนั้นคำตอบของฉันไม่ถูกต้อง
shared_ptr<>
ที่ไม่ได้ลบปวงของมันนี่คือ overkill คุณก็สามารถพูดreturn shared_ptr<Y>(this, no_op_deleter);
ที่no_op_deleter
เป็นวัตถุที่ฟังก์ชั่นการถ่ายเอกY*
และทำอะไร
m_stub = shared_ptr<Y>(this)
จะสร้างและทำลาย shared_ptr ชั่วคราวจากสิ่งนี้ทันที เมื่อคำสั่งนี้จบลงthis
จะถูกลบและการอ้างอิงที่ตามมาทั้งหมดจะห้อย
enable_shared_from_this
มันทำให้weak_ptr
ตัวของมันเอง (ประชากรโดย ctor), การกลับมาเป็นเมื่อคุณเรียกshared_ptr
shared_from_this
คุณกำลังทำซ้ำสิ่งที่enable_shared_from_this
มีอยู่แล้ว