"enable_shared_from_this" นี้มีประโยชน์อย่างไร


349

ฉันวิ่งข้ามenable_shared_from_thisในขณะที่อ่านตัวอย่าง Boost.Asio และหลังจากอ่านเอกสารฉันยังคงหลงทางว่าควรใช้สิ่งนี้อย่างถูกต้องอย่างไร ใครช่วยได้โปรดยกตัวอย่างและคำอธิบายว่าเมื่อใดที่ใช้คลาสนี้

คำตอบ:


362

จะช่วยให้คุณได้รับที่ถูกต้อง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 คุณยังสามารถรับมันได้จากที่นั่นหรือจากการเพิ่ม


202
+1 จุดสำคัญคือเทคนิค "ชัดเจน" ของการส่งคืน shared_ptr <Y> (นี่) นั้นเสียเพราะสิ่งนี้ทำให้การสร้างวัตถุ สำหรับเหตุผลที่คุณจะต้องไม่สร้างมากกว่าหนึ่ง shared_ptr จากตัวชี้ดิบเดียวกัน
j_random_hacker

3
มันควรจะตั้งข้อสังเกตว่าในC ++ 11 และต่อมาก็เป็นสิ่งที่ถูกต้องสมบูรณ์ที่จะใช้std::shared_ptrคอนสตรัคบนตัวชี้ดิบ ถ้าstd::enable_shared_from_thisมันสืบทอดมาจาก ฉันไม่รู้ว่าการปรับปรุงความหมายของ Boost ได้รับการอัปเดตเพื่อรองรับหรือไม่
Matthew

6
@MatthewHolder คุณมีคำพูดสำหรับสิ่งนี้หรือไม่? บน cppreference.com ฉันอ่าน "การสร้าง a std::shared_ptrสำหรับวัตถุที่จัดการโดยผู้อื่นแล้วstd::shared_ptrจะไม่ปรึกษาการอ้างอิงที่อ่อนแอที่เก็บไว้ภายในและดังนั้นจะนำไปสู่พฤติกรรมที่ไม่ได้กำหนด" ( en.cppreference.com/w/cpp/memory/enable_shared_from_this )
Thorbjørn Lindeijer

5
ทำไมคุณทำshared_ptr<Y> q = pไม่ได้เหรอ?
Dan M.

2
@ ThorbjørnLindeijerคุณพูดถูก C ++ 17 และใหม่กว่า การใช้งานบางอย่างไม่เป็นไปตามซีแมนทิกส์ C ++ 16 ก่อนที่จะเผยแพร่ การจัดการที่เหมาะสมสำหรับ C ++ 11 C ++ 14 std::make_shared<T>ควรจะใช้
Matthew

198

จากบทความดร. โดบส์เกี่ยวกับตัวชี้ที่อ่อนแอฉันคิดว่าตัวอย่างนี้ง่ายต่อการเข้าใจ (แหล่งที่มา: 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
}

15
ขอบคุณสิ่งนี้แสดงให้เห็นถึงปัญหาที่แก้ไขได้ดีกว่าคำตอบที่ยอมรับในปัจจุบัน
goertzenator

2
+1: คำตอบที่ดี นอกเหนือจากshared_ptr<S> sp1(new S);นี้อาจต้องการใช้งานshared_ptr<S> sp1 = make_shared<S>();แทนดูstackoverflow.com/questions/18301511/…
อรุณ

4
ฉันค่อนข้างแน่ใจว่าบรรทัดสุดท้ายควรอ่าน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 เป็นต้นไปที่ทำเช่นนี้จะโยน
AnorZaken


2
@ AnorZaken จุดดี มันจะมีประโยชน์ถ้าคุณส่งคำขอแก้ไขเพื่อทำการแก้ไข ฉันเพิ่งทำไป สิ่งที่มีประโยชน์อื่น ๆ น่าจะเป็นเพราะโปสเตอร์ไม่ได้เลือกชื่อผู้ที่มีความอ่อนไหวตามบริบท!
underscore_d

30

นี่คือคำอธิบายของฉันจากมุมมองของถั่วและ 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และลบเมื่อผู้อ้างอิง - จำนวนถึงศูนย์


1
สิ่งนี้ถูกต้องและส่วนที่สำคัญจริง ๆ ก็So, when you first create...เพราะว่ามันเป็นข้อกำหนด (อย่างที่คุณบอกว่า weak_ptr ไม่ได้ถูกเตรียมใช้งานจนกว่าคุณจะส่งตัวชี้วัตถุไปยัง ctor shared_ptr!) และข้อกำหนดนี้เป็นสิ่งที่ผิดพลาดอย่างน่ากลัวถ้าคุณเป็น ไม่ระวัง หากคุณไม่ได้สร้าง shared_ptr ก่อนการโทรshared_from_thisคุณจะได้รับ UB เช่นเดียวกันหากคุณสร้าง shared_ptr มากกว่าหนึ่งรายการคุณจะได้รับ UB ด้วย คุณต้องทำให้แน่ใจว่าคุณสร้าง shared_ptr เพียงครั้งเดียว
AnorZaken

2
กล่าวอีกนัยหนึ่งความคิดทั้งหมดของการenable_shared_from_thisเปราะเริ่มต้นตั้งแต่จุดที่จะสามารถได้รับshared_ptr<T>จาก a T*แต่ในความเป็นจริงเมื่อคุณได้รับตัวชี้T* tโดยทั่วไปจะไม่ปลอดภัยที่จะคิดอะไรเกี่ยวกับการแบ่งปันหรือไม่และ การเดาผิดคือ UB
AnorZaken

" internal weak_ptr รับค่าเริ่มต้นด้วย refcount ของ 1 " ptr ที่อ่อนแอถึง T ไม่ได้เป็นเจ้าของสมาร์ท ptr ถึง T PTR ที่อ่อนแอเป็นเจ้าของสมาร์ทอ้างอิงถึงข้อมูลเพียงพอที่จะทำให้เป็นเจ้าของ PTR ที่เป็น "คัดลอก" ของ PTR อื่น ๆ PTR ที่อ่อนแอไม่มีการอ้างอิง สามารถเข้าถึงการนับการอ้างอิงเช่นเดียวกับการอ้างอิงทั้งหมดที่เป็นเจ้าของ
curiousguy

3

โปรดทราบว่าการใช้ boost :: intrusive_ptr ไม่ประสบปัญหานี้ นี่เป็นวิธีที่สะดวกกว่าในการแก้ไขปัญหานี้


ใช่ แต่enable_shared_from_thisช่วยให้คุณสามารถทำงานร่วมกับ API shared_ptr<>ซึ่งเฉพาะยอมรับ ในความคิดของฉันเช่น API มักจะทำผิด (มันจะดีกว่าที่จะปล่อยให้สิ่งที่สูงกว่าในกองเป็นเจ้าของหน่วยความจำ) แต่ถ้าคุณถูกบังคับให้ทำงานกับ API เช่นนี้เป็นตัวเลือกที่ดี
cdunn2001

2
ดีกว่าที่จะอยู่ในมาตรฐานเท่าที่คุณจะทำได้
Sergei

3

มันเหมือนกันใน 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นี้จะทำงานถ้าวัตถุเหล่านี้มีการจัดการเสมอโดย คุณอาจต้องการเปลี่ยนอินเทอร์เฟซเพื่อให้แน่ใจว่าเป็นกรณีนี้
curiousguy

1
คุณถูกต้องอย่าง @curtguy สิ่งนี้ไปโดยไม่บอก ฉันยังต้องการพิมพ์ shared_ptr ทั้งหมดของฉันเพื่อปรับปรุงความสามารถในการอ่านได้เมื่อกำหนด API สาธารณะของฉัน ตัวอย่างเช่นแทนที่จะstd::shared_ptr<Node> getParent const()ฉันจะเปิดเผยตามปกติNodePtr getParent const()แทน หากคุณต้องการเข้าถึงตัวชี้ raw ภายใน (ตัวอย่างที่ดีที่สุด: การจัดการกับไลบรารี C) มีอยู่std::shared_ptr<T>::getสำหรับสิ่งที่ฉันเกลียดพูดถึงเพราะฉันได้ accessor ตัวชี้ดิบนี้ใช้หลายครั้งด้วยเหตุผลที่ผิด
mchiasson

-3

อีกวิธีหนึ่งคือการเพิ่มสมาชิกเข้าไปในweak_ptr<Y> m_stub class Yจากนั้นเขียน:

shared_ptr<Y> Y::f()
{
    return m_stub.lock();
}

มีประโยชน์เมื่อคุณไม่สามารถเปลี่ยนชั้นเรียนที่คุณได้รับ (เช่นขยายห้องสมุดของผู้อื่น) อย่าลืมที่จะเริ่มต้นสมาชิกเช่นโดยm_stub = shared_ptr<Y>(this)มันถูกต้องแม้ในช่วงตัวสร้าง

มันก็โอเคถ้ามีสตับมากเช่นนี้ในลำดับชั้นการสืบทอดมันจะไม่ป้องกันการทำลายของวัตถุ

แก้ไข:ตามที่ผู้ใช้โนเบิลชี้ให้เห็นอย่างถูกต้องรหัสจะทำลายวัตถุ Y เมื่อการมอบหมายเสร็จสิ้นและตัวแปรชั่วคราวจะถูกทำลาย ดังนั้นคำตอบของฉันไม่ถูกต้อง


4
หากความตั้งใจของคุณที่นี่คือการสร้างshared_ptr<>ที่ไม่ได้ลบปวงของมันนี่คือ overkill คุณก็สามารถพูดreturn shared_ptr<Y>(this, no_op_deleter);ที่no_op_deleterเป็นวัตถุที่ฟังก์ชั่นการถ่ายเอกY*และทำอะไร
John Zwinck

2
ดูเหมือนว่าไม่น่าจะเป็นวิธีแก้ปัญหาที่ใช้งานได้ m_stub = shared_ptr<Y>(this)จะสร้างและทำลาย shared_ptr ชั่วคราวจากสิ่งนี้ทันที เมื่อคำสั่งนี้จบลงthisจะถูกลบและการอ้างอิงที่ตามมาทั้งหมดจะห้อย
สูงศักดิ์

2
ผู้เขียนยอมรับว่าคำตอบนี้ผิดดังนั้นเขาจึงอาจลบได้ แต่เขาเข้าสู่ระบบครั้งล่าสุดใน 4.5 ปีดังนั้นจึงไม่มีแนวโน้มที่จะทำเช่นนั้น - ผู้ที่มีอำนาจสูงกว่าสามารถเอาปลาเฮอริ่งแดงนี้ออกได้ไหม
Tom Goodfellow

ถ้าคุณมองไปที่การดำเนินงานของenable_shared_from_thisมันทำให้weak_ptrตัวของมันเอง (ประชากรโดย ctor), การกลับมาเป็นเมื่อคุณเรียกshared_ptr shared_from_thisคุณกำลังทำซ้ำสิ่งที่enable_shared_from_thisมีอยู่แล้ว
mchiasson
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.