ฉันวิ่งข้าม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_ptrweak_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มีอยู่แล้ว